diff --git a/frontend/apps/reader/modules/readerdevicestatus.lua b/frontend/apps/reader/modules/readerdevicestatus.lua index f7a65e791..ebdec5bd9 100644 --- a/frontend/apps/reader/modules/readerdevicestatus.lua +++ b/frontend/apps/reader/modules/readerdevicestatus.lua @@ -16,7 +16,7 @@ memory_confirm_box = nil, function ReaderDeviceStatus:init() if Device:hasBattery() then - self.battery_interval = G_reader_settings:readSetting("device_status_battery_interval", 10) + self.battery_interval_m = G_reader_settings:readSetting("device_status_battery_interval_minutes", 10) self.battery_threshold = G_reader_settings:readSetting("device_status_battery_threshold", 20) self.battery_threshold_high = G_reader_settings:readSetting("device_status_battery_threshold_high", 100) self.checkLowBatteryLevel = function() @@ -45,13 +45,13 @@ function ReaderDeviceStatus:init() UIManager:show(self.battery_confirm_box) end end - UIManager:scheduleIn(self.battery_interval * 60, self.checkLowBatteryLevel) + UIManager:scheduleIn(self.battery_interval_m * 60, self.checkLowBatteryLevel) end self:startBatteryChecker() end if not Device:isAndroid() then - self.memory_interval = G_reader_settings:readSetting("device_status_memory_interval", 5) + self.memory_interval_m = G_reader_settings:readSetting("device_status_memory_interval_minutes", 5) self.memory_threshold = G_reader_settings:readSetting("device_status_memory_threshold", 100) self.checkHighMemoryUsage = function() local statm = io.open("/proc/self/statm", "r") @@ -103,7 +103,7 @@ function ReaderDeviceStatus:init() end end end - UIManager:scheduleIn(self.memory_interval * 60, self.checkHighMemoryUsage) + UIManager:scheduleIn(self.memory_interval_m * 60, self.checkHighMemoryUsage) end self:startMemoryChecker() end @@ -136,7 +136,7 @@ function ReaderDeviceStatus:addToMainMenu(menu_items) table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() - return T(_("Check interval: %1 min"), self.battery_interval) + return T(_("Check interval: %1 min"), self.battery_interval_m) end, enabled_func = function() return G_reader_settings:isTrue("device_status_battery_alarm") @@ -144,18 +144,18 @@ function ReaderDeviceStatus:addToMainMenu(menu_items) keep_menu_open = true, callback = function(touchmenu_instance) UIManager:show(SpinWidget:new{ - value = self.battery_interval, + value = self.battery_interval_m, value_min = 1, value_max = 60, default_value = 10, value_hold_step = 5, title_text = _("Battery check interval"), callback = function(spin) - self.battery_interval = spin.value - G_reader_settings:saveSetting("device_status_battery_interval", self.battery_interval) + self.battery_interval_m = spin.value + G_reader_settings:saveSetting("device_status_battery_interval_minutes", self.battery_interval_m) touchmenu_instance:updateItems() powerd:setDismissBatteryStatus(false) - UIManager:scheduleIn(self.battery_interval * 60, self.checkLowBatteryLevel) + UIManager:scheduleIn(self.battery_interval_m * 60, self.checkLowBatteryLevel) end, }) end, @@ -228,7 +228,7 @@ High level threshold is checked when the device is charging.]]), table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() - return T(_("Check interval: %1 min"), self.memory_interval) + return T(_("Check interval: %1 min"), self.memory_interval_m) end, enabled_func = function() return G_reader_settings:isTrue("device_status_memory_alarm") @@ -236,17 +236,17 @@ High level threshold is checked when the device is charging.]]), keep_menu_open = true, callback = function(touchmenu_instance) UIManager:show(SpinWidget:new{ - value = self.memory_interval, + value = self.memory_interval_m, value_min = 1, value_max = 60, default_value = 5, value_hold_step = 5, title_text = _("Memory check interval"), callback = function(spin) - self.memory_interval = spin.value - G_reader_settings:saveSetting("device_status_memory_interval", self.memory_interval) + self.memory_interval_m = spin.value + G_reader_settings:saveSetting("device_status_memory_interval_minutes", self.memory_interval_m) touchmenu_instance:updateItems() - UIManager:scheduleIn(self.memory_interval * 60, self.checkHighMemoryUsage) + UIManager:scheduleIn(self.memory_interval_m * 60, self.checkHighMemoryUsage) end, }) end, diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index eafc7bf22..780091fad 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -13,11 +13,11 @@ local LuaData = require("luadata") local MultiConfirmBox = require("ui/widget/multiconfirmbox") local NetworkMgr = require("ui/network/manager") local SortWidget = require("ui/widget/sortwidget") -local TimeVal = require("ui/timeval") local Trapper = require("ui/trapper") local UIManager = require("ui/uimanager") local ffiUtil = require("ffi/util") local logger = require("logger") +local time = require("ui/time") local util = require("util") local _ = require("gettext") local T = ffiUtil.template @@ -111,7 +111,7 @@ function ReaderDictionary:init() -- Allow quick interruption or dismiss of search result window -- with tap if done before this delay. After this delay, the -- result window is shown and dismiss prevented for a few 100ms - self.quick_dismiss_before_delay = TimeVal:new{ sec = 3, usec = 0 } + self.quick_dismiss_before_delay = time.s(3) -- Gather info about available dictionaries if not available_ifos then @@ -931,9 +931,10 @@ function ReaderDictionary:stardictLookup(word, dict_names, fuzzy_search, boxes, self:showLookupInfo(word, self.lookup_msg_delay) - self._lookup_start_tv = UIManager:getTime() + self._lookup_start_time = UIManager:getTime() local results = self:startSdcv(word, dict_names, fuzzy_search) - if results and results.lookup_cancelled and TimeVal:now() - self._lookup_start_tv <= self.quick_dismiss_before_delay then + if results and results.lookup_cancelled + and (time.now() - self._lookup_start_time) <= self.quick_dismiss_before_delay then -- If interrupted quickly just after launch, don't display anything -- (this might help avoiding refreshes and the need to dismiss -- after accidental long-press when holding a device). @@ -991,7 +992,8 @@ function ReaderDictionary:showDict(word, results, boxes, link) self:dismissLookupInfo() if results and results[1] then UIManager:show(self.dict_window) - if not results.lookup_cancelled and self._lookup_start_tv and TimeVal:now() - self._lookup_start_tv > self.quick_dismiss_before_delay then + if not results.lookup_cancelled and self._lookup_start_time + and (time.now() - self._lookup_start_time) > self.quick_dismiss_before_delay then -- If the search took more than a few seconds to be done, discard -- queued and coming up events to avoid a voluntary dismissal -- (because the user felt the result would not come) to kill the diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 370952b00..28424af62 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -7,7 +7,6 @@ local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local Notification = require("ui/widget/notification") local TextViewer = require("ui/widget/textviewer") -local TimeVal = require("ui/timeval") local Translator = require("ui/translator") local UIManager = require("ui/uimanager") local dbg = require("dbg") @@ -15,6 +14,7 @@ local logger = require("logger") local util = require("util") local Size = require("ui/size") local ffiUtil = require("ffi/util") +local time = require("ui/time") local _ = require("gettext") local C_ = _.pgettext local T = require("ffi/util").template @@ -51,7 +51,7 @@ function ReaderHighlight:init() self._start_indicator_highlight = false self._current_indicator_pos = nil self._previous_indicator_pos = nil - self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = TimeVal:now()} + self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = time:now()} if Device:hasDPad() then -- Used for text selection with dpad/keys @@ -897,9 +897,9 @@ dbg:guard(ReaderHighlight, "onShowHighlightMenu", function ReaderHighlight:_resetHoldTimer(clear) if clear then - self.hold_last_tv = nil + self.hold_last_time = nil else - self.hold_last_tv = UIManager:getTime() + self.hold_last_time = UIManager:getTime() end end @@ -1423,14 +1423,14 @@ function ReaderHighlight:onHoldRelease() end local long_final_hold = false - if self.hold_last_tv then - local hold_duration = TimeVal:now() - self.hold_last_tv - local long_hold_threshold = G_reader_settings:readSetting("highlight_long_hold_threshold", 3) - if hold_duration > TimeVal:new{ sec = long_hold_threshold, usec = 0 } then + if self.hold_last_time then + local hold_duration = time.now() - self.hold_last_time + local long_hold_threshold_s = G_reader_settings:readSetting("highlight_long_hold_threshold", 3) -- seconds + if hold_duration > time.s(long_hold_threshold_s) then -- We stayed 3 seconds before release without updating selection long_final_hold = true end - self.hold_last_tv = nil + self.hold_last_time = nil end if self.is_word_selection then -- single-word selection if long_final_hold or G_reader_settings:isTrue("highlight_action_on_single_word") then @@ -1947,14 +1947,14 @@ function ReaderHighlight:onMoveHighlightIndicator(args) rect.x = rect.x + quick_move_distance_dx * dx rect.y = rect.y + quick_move_distance_dy * dy else - local now = TimeVal:now() + local now = time:now() if dx == self._last_indicator_move_args.dx and dy == self._last_indicator_move_args.dy then local diff = now - self._last_indicator_move_args.time -- if press same arrow key in 1 second, speed up -- double press: 4 single move distances, usually move to next word or line -- triple press: 16 single distances, usually skip several words or lines -- quadruple press: 54 single distances, almost move to screen edge - if diff < TimeVal:new{ sec = 1, usec = 0 } then + if diff < time.s(1) then move_distance = self._last_indicator_move_args.distance * 4 end end @@ -1998,7 +1998,7 @@ function ReaderHighlight:_createHighlightGesture(gesture) return { ges = gesture, pos = point, - time = TimeVal:realtime(), + time = time.realtime(), } end diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index 8b35be600..7d8cb87eb 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -5,16 +5,15 @@ local Geom = require("ui/geometry") local InputContainer = require("ui/widget/container/inputcontainer") local Math = require("optmath") local ReaderZooming = require("apps/reader/modules/readerzooming") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local bit = require("bit") local logger = require("logger") local util = require("util") +local time = require("ui/time") local _ = require("gettext") local Input = Device.input local Screen = Device.screen - local function copyPageState(page_state) return { page = page_state.page, @@ -98,7 +97,7 @@ function ReaderPaging:init() {"0"}, doc = "go to end", event = "GotoPercent", args = 100, } end - self.pan_interval = TimeVal:new{ usec = 1000000 / self.pan_rate } + self.pan_interval = time.s(1 / self.pan_rate) self.number_of_pages = self.ui.document.info.number_of_pages end @@ -321,9 +320,9 @@ function ReaderPaging:bookmarkFlipping(flipping_page, flipping_ges) UIManager:setDirty(self.view.dialog, "partial") end -function ReaderPaging:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay) +function ReaderPaging:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay_ms) self.scroll_method = scroll_method - self.scroll_activation_delay = TimeVal:new{ usec = scroll_activation_delay * 1000 } + self.scroll_activation_delay = time.ms(scroll_activation_delay_ms) if inertial_scroll_enabled then self.ui.scrolling:setInertialScrollCallbacks( function(distance) -- do_scroll_callback @@ -408,7 +407,7 @@ function ReaderPaging:onPan(_, ges) self._pan_has_scrolled = false self._pan_prev_relative_y = 0 self._pan_to_scroll_later = 0 - self._pan_real_last_time = TimeVal.zero + self._pan_real_last_time = 0 if ges.mousewheel_direction then self._pan_activation_time = false else diff --git a/frontend/apps/reader/modules/readerrolling.lua b/frontend/apps/reader/modules/readerrolling.lua index 83c8ea3f6..6420958e4 100644 --- a/frontend/apps/reader/modules/readerrolling.lua +++ b/frontend/apps/reader/modules/readerrolling.lua @@ -7,10 +7,10 @@ local InputContainer = require("ui/widget/container/inputcontainer") local ProgressWidget = require("ui/widget/progresswidget") local ReaderPanning = require("apps/reader/modules/readerpanning") local Size = require("ui/size") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local bit = require("bit") local logger = require("logger") +local time = require("ui/time") local _ = require("gettext") local Screen = Device.screen local T = require("ffi/util").template @@ -112,7 +112,7 @@ function ReaderRolling:init() {"0"}, doc = "go to end", event = "GotoPercent", args = 100, } end - self.pan_interval = TimeVal:new{ usec = 1000000 / self.pan_rate } + self.pan_interval = time.s(1 / self.pan_rate) table.insert(self.ui.postInitCallback, function() self.rendering_hash = self.ui.document:getDocumentRenderingHash() @@ -403,9 +403,9 @@ function ReaderRolling:getLastPercent() end end -function ReaderRolling:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay) +function ReaderRolling:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay_ms) self.scroll_method = scroll_method - self.scroll_activation_delay = TimeVal:new{ usec = scroll_activation_delay * 1000 } + self.scroll_activation_delay = time.ms(scroll_activation_delay_ms) if inertial_scroll_enabled then self.ui.scrolling:setInertialScrollCallbacks( function(distance) -- do_scroll_callback @@ -478,7 +478,7 @@ function ReaderRolling:onPan(_, ges) self._pan_has_scrolled = false self._pan_prev_relative_y = 0 self._pan_to_scroll_later = 0 - self._pan_real_last_time = TimeVal.zero + self._pan_real_last_time = 0 if ges.mousewheel_direction then self._pan_activation_time = false else @@ -1173,8 +1173,8 @@ function ReaderRolling:handleEngineCallback(ev, ...) -- ignore other events end -local ENGINE_PROGRESS_INITIAL_DELAY = TimeVal:new{ sec = 2, usec = 0 } -local ENGINE_PROGRESS_UPDATE_DELAY = TimeVal:new{ sec = 0, usec = 500000 } +local ENGINE_PROGRESS_INITIAL_DELAY = time.s(2) +local ENGINE_PROGRESS_UPDATE_DELAY = time.ms(500) function ReaderRolling:showEngineProgress(percent) if G_reader_settings and G_reader_settings:isFalse("cre_show_progress") then @@ -1186,7 +1186,7 @@ function ReaderRolling:showEngineProgress(percent) end if percent then - local now = TimeVal:now() + local now = time.now() if self.engine_progress_update_not_before and now < self.engine_progress_update_not_before then return end diff --git a/frontend/apps/reader/modules/readerscrolling.lua b/frontend/apps/reader/modules/readerscrolling.lua index 93848f11b..cb53b0ffd 100644 --- a/frontend/apps/reader/modules/readerscrolling.lua +++ b/frontend/apps/reader/modules/readerscrolling.lua @@ -1,9 +1,9 @@ local Device = require("device") local Event = require("ui/event") local InputContainer = require("ui/widget/container/inputcontainer") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local logger = require("logger") +local time = require("ui/time") local _ = require("gettext") local T = require("ffi/util").template local Screen = Device.screen @@ -22,7 +22,7 @@ local ReaderScrolling = InputContainer:new{ SCROLL_METHOD_ON_RELEASE = SCROLL_METHOD_ON_RELEASE, scroll_method = SCROLL_METHOD_CLASSIC, - scroll_activation_delay = 0, -- 0 ms + scroll_activation_delay_ms = 0, -- 0 ms inertial_scroll = false, pan_rate = 30, -- default 30 ops, will be adjusted in readerui @@ -30,7 +30,7 @@ local ReaderScrolling = InputContainer:new{ -- go at ending scrolling soon when we reach steps smaller than this end_scroll_dist = Screen:scaleBySize(10), -- no inertial scrolling if 300ms pause without any movement before release - pause_before_release_cancel_duration = TimeVal:new{ sec = 0, usec = 300000 }, + pause_before_release_cancel_duration = time.ms(300), -- Callbacks to be updated by readerrolling or readerpaging _do_scroll_callback = function(distance) return false end, @@ -66,14 +66,14 @@ function ReaderScrolling:init() -- we miss a first touch event. -- We can keep it obsolete, which will result in a long -- duration and a small/zero velocity that won't hurt. - self._last_manual_scroll_timev = TimeVal.zero + self._last_manual_scroll_timev = 0 self:_setupAction() end self.ui.menu:registerToMainMenu(self) end -function ReaderScrolling:getDefaultScrollActivationDelay() +function ReaderScrolling:getDefaultScrollActivationDelay_ms() if (self.ui.gestures and self.ui.gestures.multiswipes_enabled) or G_reader_settings:readSetting("activate_menu") ~= "tap" then -- If swipes to show menu or multiswipes are enabled, higher default @@ -143,11 +143,11 @@ This is interesting on eInk if you only pan to better adjust page vertical posit }, { text_func = function() - return T(_("Activation delay: %1 ms"), self.scroll_activation_delay) + return T(_("Activation delay: %1 ms"), self.scroll_activation_delay_ms) end, keep_menu_open = true, callback = function(touchmenu_instance) - local scroll_activation_delay_default = self:getDefaultScrollActivationDelay() + local scroll_activation_delay_default_ms = self:getDefaultScrollActivationDelay_ms() local SpinWidget = require("ui/widget/spinwidget") local widget = SpinWidget:new{ title_text = _("Scroll activation delay"), @@ -155,17 +155,17 @@ This is interesting on eInk if you only pan to better adjust page vertical posit A delay can be used to avoid scrolling when swipes or multiswipes are intended. The delay value is in milliseconds and can range from 0 to 2000 (2 seconds). -Default value: %1 ms]]), scroll_activation_delay_default), +Default value: %1 ms]]), scroll_activation_delay_default_ms), width = math.floor(Screen:getWidth() * 0.75), - value = self.scroll_activation_delay, + value = self.scroll_activation_delay_ms, value_min = 0, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set delay"), - default_value = scroll_activation_delay_default, + default_value = scroll_activation_delay_default_ms, callback = function(spin) - self.scroll_activation_delay = spin.value + self.scroll_activation_delay_ms = spin.value self:applyScrollSettings() if touchmenu_instance then touchmenu_instance:updateItems() end end @@ -195,18 +195,18 @@ end function ReaderScrolling:onReaderReady() -- We don't know if the gestures plugin is loaded in :init(), but we know it here - self.scroll_activation_delay = G_reader_settings:readSetting("scroll_activation_delay") - or self:getDefaultScrollActivationDelay() + self.scroll_activation_delay_ms = G_reader_settings:readSetting("scroll_activation_delay") + or self:getDefaultScrollActivationDelay_ms() self:applyScrollSettings() end function ReaderScrolling:applyScrollSettings() G_reader_settings:saveSetting("scroll_method", self.scroll_method) G_reader_settings:saveSetting("inertial_scroll", self.inertial_scroll) - if self.scroll_activation_delay == self:getDefaultScrollActivationDelay() then + if self.scroll_activation_delay_ms == self:getDefaultScrollActivationDelay_ms() then G_reader_settings:delSetting("scroll_activation_delay") else - G_reader_settings:saveSetting("scroll_activation_delay", self.scroll_activation_delay) + G_reader_settings:saveSetting("scroll_activation_delay", self.scroll_activation_delay_ms) end if self.scroll_method == self.SCROLL_METHOD_CLASSIC then self._inertial_scroll_enabled = self.inertial_scroll @@ -215,7 +215,7 @@ function ReaderScrolling:applyScrollSettings() end self:setupTouchZones() self.ui:handleEvent(Event:new("ScrollSettingsUpdated", self.scroll_method, - self._inertial_scroll_enabled, self.scroll_activation_delay)) + self._inertial_scroll_enabled, self.scroll_activation_delay_ms)) end function ReaderScrolling:setupTouchZones() @@ -339,14 +339,14 @@ function ReaderScrolling:_setupAction() self._last_manual_scroll_dy = 0 return false end - if self._last_manual_scroll_duration:isZero() or self._last_manual_scroll_dy == 0 then + if self._last_manual_scroll_duration == 0 or self._last_manual_scroll_dy == 0 then return false end -- Initial velocity is the one of the last pan scroll given to accountManualScroll() - local delay = self._last_manual_scroll_duration:tousecs() - if delay < 1 then delay = 1 end -- safety check - self._velocity = self._last_manual_scroll_dy * 1000000 / delay + local delay_us = time.to_us(self._last_manual_scroll_duration) + if delay_us < 1 then delay_us = 1 end -- safety check + self._velocity = self._last_manual_scroll_dy * time.s(1 / delay_us) self._last_manual_scroll_dy = 0 self._inertial_scroll_action_scheduled = true diff --git a/frontend/apps/reader/modules/readerthumbnail.lua b/frontend/apps/reader/modules/readerthumbnail.lua index 5ea944226..d57120766 100644 --- a/frontend/apps/reader/modules/readerthumbnail.lua +++ b/frontend/apps/reader/modules/readerthumbnail.lua @@ -314,15 +314,15 @@ function ReaderThumbnail:startTileGeneration(request) local scale_factor = math.min(request.width / bb:getWidth(), request.height / bb:getHeight()) local target_w = math.floor(bb:getWidth() * scale_factor) local target_h = math.floor(bb:getHeight() * scale_factor) - -- local TimeVal = require("ui/timeval") - -- local start_tv = TimeVal:now() + -- local time = require("ui/time") + -- local start_time = time.now() local tile = TileCacheItem:new{ bb = RenderImage:scaleBlitBuffer(bb, target_w, target_h, true), pageno = request.page, } tile.size = tonumber(tile.bb.stride) * tile.bb.h -- logger.info("tile size", tile.bb.w, tile.bb.h, "=>", tile.size) - -- logger.info(string.format(" scaling took %.3f seconds, %d bpp", TimeVal:getDuration(start_tv), tile.bb:getBpp())) + -- logger.info(string.format(" scaling took %.3f seconds, %d bpp", time.to_s(time.since(start_time)), tile.bb:getBpp())) -- bb:free() -- no need to spend time freeing, we're dying soon anyway! ffiutil.writeToFD(child_write_fd, self.codec.serialize(tile:totable()), true) @@ -343,8 +343,8 @@ function ReaderThumbnail:checkTileGeneration(request) local subprocess_done = ffiutil.isSubProcessDone(pid) logger.dbg("subprocess_done:", subprocess_done, " stuff_to_read:", stuff_to_read) if stuff_to_read then - -- local TimeVal = require("ui/timeval") - -- local start_tv = TimeVal:now() + -- local time = require("ui/time") + -- local start_time = time.now() local result, err = self.codec.deserialize(ffiutil.readAllFromFD(parent_read_fd)) if result then local tile = TileCacheItem:new{} @@ -361,7 +361,7 @@ function ReaderThumbnail:checkTileGeneration(request) request.when_generated_callback(nil, request.batch_id, true) end end - -- logger.info(string.format(" parsing result from subprocess took %.3f seconds", TimeVal:getDuration(start_tv))) + -- logger.info(string.format(" parsing result from subprocess took %.3f seconds", time.to_s(time.since(start_time)))) if not subprocess_done then table.insert(pids_to_collect, pid) return false, true diff --git a/frontend/apps/reader/modules/readerview.lua b/frontend/apps/reader/modules/readerview.lua index 475c8a7a4..a484eecd4 100644 --- a/frontend/apps/reader/modules/readerview.lua +++ b/frontend/apps/reader/modules/readerview.lua @@ -14,12 +14,12 @@ local OverlapGroup = require("ui/widget/overlapgroup") local ReaderDogear = require("apps/reader/modules/readerdogear") local ReaderFlipping = require("apps/reader/modules/readerflipping") local ReaderFooter = require("apps/reader/modules/readerfooter") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local dbg = require("dbg") local logger = require("logger") local optionsutil = require("ui/data/optionsutil") local Size = require("ui/size") +local time = require("ui/time") local _ = require("gettext") local Screen = Device.screen local T = require("ffi/util").template @@ -76,7 +76,7 @@ local ReaderView = OverlapGroup:extend{ -- in flipping state flipping_visible = false, -- to ensure periodic flush of settings - settings_last_save_btv = nil, + settings_last_save_time = nil, -- might be directly updated by readerpaging/readerrolling when -- they handle some panning/scrolling, to request "fast" refreshes currently_scrolling = false, @@ -1038,17 +1038,17 @@ end function ReaderView:onReaderReady() self.ui.doc_settings:delSetting("docsettings_reset_done") - self.settings_last_save_btv = UIManager:getElapsedTimeSinceBoot() + self.settings_last_save_time = UIManager:getElapsedTimeSinceBoot() end function ReaderView:onResume() -- As settings were saved on suspend, reset this on resume, -- as there's no need for a possibly immediate save. - self.settings_last_save_btv = UIManager:getElapsedTimeSinceBoot() + self.settings_last_save_time = UIManager:getElapsedTimeSinceBoot() end function ReaderView:checkAutoSaveSettings() - if not self.settings_last_save_btv then -- reader not yet ready + if not self.settings_last_save_time then -- reader not yet ready return end if G_reader_settings:nilOrFalse("auto_save_settings_interval_minutes") then @@ -1056,11 +1056,11 @@ function ReaderView:checkAutoSaveSettings() return end - local interval = G_reader_settings:readSetting("auto_save_settings_interval_minutes") - interval = TimeVal:new{ sec = interval*60, usec = 0 } - local now_btv = UIManager:getElapsedTimeSinceBoot() - if now_btv - self.settings_last_save_btv >= interval then - self.settings_last_save_btv = now_btv + local interval_m = G_reader_settings:readSetting("auto_save_settings_interval_minutes") + local interval = time.s(interval_m * 60) + local now = UIManager:getElapsedTimeSinceBoot() + if now - self.settings_last_save_time >= interval then + self.settings_last_save_time = now -- I/O, delay until after the pageturn UIManager:tickAfterNext(function() self.ui:saveSettings() diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 72bc6bafa..a6aeb0e14 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -56,11 +56,11 @@ local ReaderWikipedia = require("apps/reader/modules/readerwikipedia") local ReaderZooming = require("apps/reader/modules/readerzooming") local Screenshoter = require("ui/widget/screenshoter") local SettingsMigration = require("ui/data/settings_migration") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local ffiUtil = require("ffi/util") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") +local time = require("ui/time") local util = require("util") local _ = require("gettext") local Screen = require("device").screen @@ -290,19 +290,19 @@ function ReaderUI:init() end -- make sure we render document first before calling any callback self:registerPostInitCallback(function() - local start_tv = TimeVal:now() + local start_time = time.now() if not self.document:loadDocument() then self:dealWithLoadDocumentFailure() end - logger.dbg(string.format(" loading took %.3f seconds", TimeVal:getDuration(start_tv))) + logger.dbg(string.format(" loading took %.3f seconds", time.to_s(time.since(start_time)))) -- used to read additional settings after the document has been -- loaded (but not rendered yet) self:handleEvent(Event:new("PreRenderDocument", self.doc_settings)) - start_tv = TimeVal:now() + start_time = time.now() self.document:render() - logger.dbg(string.format(" rendering took %.3f seconds", TimeVal:getDuration(start_tv))) + logger.dbg(string.format(" rendering took %.3f seconds", time.to_s(time.since(start_time)))) -- Uncomment to output the built DOM (for debugging) -- logger.dbg(self.document:getHTMLFromXPointer(".0", 0x6830)) diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index b189c1a53..80a206b69 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -6,7 +6,6 @@ This module defines stubs for common methods. local DataStorage = require("datastorage") local Geom = require("ui/geometry") -local TimeVal = require("ui/timeval") local logger = require("logger") local util = require("util") local _ = require("gettext") @@ -71,10 +70,10 @@ local Device = { canSuspend = yes, canStandby = no, canPowerSaveWhileCharging = no, - total_standby_tv = TimeVal.zero, -- total time spent in standby - last_standby_tv = TimeVal.zero, - total_suspend_tv = TimeVal.zero, -- total time spent in suspend - last_suspend_tv = TimeVal.zero, + total_standby_time = 0, -- total time spent in standby + last_standby_time = 0, + total_suspend_time = 0, -- total time spent in suspend + last_suspend_time = 0, canReboot = no, canPowerOff = no, canAssociateFileExtensions = no, diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua index 4f0635347..acec816bb 100644 --- a/frontend/device/generic/powerd.lua +++ b/frontend/device/generic/powerd.lua @@ -1,7 +1,7 @@ local UIManager -- will be updated when available local Math = require("optmath") -local TimeVal = require("ui/timeval") local logger = require("logger") +local time = require("ui/time") local BasePowerD = { fl_min = 0, -- min frontlight intensity fl_max = 10, -- max frontlight intensity @@ -13,8 +13,8 @@ local BasePowerD = { aux_batt_capacity = 0, -- auxiliary battery capacity device = nil, -- device object - last_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0}, -- timestamp of last pull - last_aux_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0}, -- timestamp of last pull + last_capacity_pull_time = time.s(-61), -- timestamp of last pull + last_aux_capacity_pull_time = time.s(-61), -- timestamp of last pull is_fl_on = false, -- whether the frontlight is on } @@ -216,17 +216,17 @@ end function BasePowerD:getCapacity() -- BasePowerD is loaded before UIManager. -- Nothing *currently* calls this before UIManager is actually loaded, but future-proof this anyway. - local now_btv + local now if UIManager then - now_btv = UIManager:getElapsedTimeSinceBoot() + now = UIManager:getElapsedTimeSinceBoot() else -- Add time the device was in standby and suspend - now_btv = TimeVal:now() + self.device.total_standby_tv + self.device.total_suspend_tv + now = time.now() + self.device.total_standby_time + self.device.total_suspend_time end - if (now_btv - self.last_capacity_pull_time):tonumber() >= 60 then + if now - self.last_capacity_pull_time >= time.s(60) then self.batt_capacity = self:getCapacityHW() - self.last_capacity_pull_time = now_btv + self.last_capacity_pull_time = now end return self.batt_capacity end @@ -240,29 +240,29 @@ function BasePowerD:isCharged() end function BasePowerD:getAuxCapacity() - local now_btv + local now if UIManager then - now_btv = UIManager:getElapsedTimeSinceBoot() + now = UIManager:getElapsedTimeSinceBoot() else -- Add time the device was in standby and suspend - now_btv = TimeVal:now() + self.device.total_standby_tv + self.device.total_suspend_tv + now = time.now() + self.device.total_standby_time + self.device.total_suspend_time end - if (now_btv - self.last_aux_capacity_pull_time):tonumber() >= 60 then + if now - self.last_aux_capacity_pull_time >= time.s(60) then local aux_batt_capa = self:getAuxCapacityHW() -- If the read failed, don't update our cache, and retry next time. if aux_batt_capa then self.aux_batt_capacity = aux_batt_capa - self.last_aux_capacity_pull_time = now_btv + self.last_aux_capacity_pull_time = now end end return self.aux_batt_capacity end function BasePowerD:invalidateCapacityCache() - self.last_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0} - self.last_aux_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0} + self.last_capacity_pull_time = time.s(-61) + self.last_aux_capacity_pull_time = self.last_capacity_pull_time end function BasePowerD:isAuxCharging() diff --git a/frontend/device/gesturedetector.lua b/frontend/device/gesturedetector.lua index 1ce6232b6..d94461e45 100644 --- a/frontend/device/gesturedetector.lua +++ b/frontend/device/gesturedetector.lua @@ -33,7 +33,7 @@ a touch event should have following format: id = 46, x = 0, y = 1, - timev = TimeVal:new{...}, + timev = time.s(123.23), } Don't confuse `tev` with raw evs from kernel, `tev` is built according to ev. @@ -43,8 +43,8 @@ detection result when you feed a touch release event to it. --]] local Geom = require("ui/geometry") -local TimeVal = require("ui/timeval") local logger = require("logger") +local time = require("ui/time") local util = require("util") -- We're going to need some clockid_t constants @@ -52,33 +52,22 @@ local ffi = require("ffi") local C = ffi.C require("ffi/posix_h") --- default values (all the time parameters are in microseconds) -local TAP_INTERVAL = 0 * 1000 -local DOUBLE_TAP_INTERVAL = 300 * 1000 -local TWO_FINGER_TAP_DURATION = 300 * 1000 -local HOLD_INTERVAL = 500 * 1000 -local SWIPE_INTERVAL = 900 * 1000 --- current values -local ges_tap_interval = G_reader_settings:readSetting("ges_tap_interval") or TAP_INTERVAL -ges_tap_interval = TimeVal:new{ usec = ges_tap_interval } -local ges_double_tap_interval = G_reader_settings:readSetting("ges_double_tap_interval") or DOUBLE_TAP_INTERVAL -ges_double_tap_interval = TimeVal:new{ usec = ges_double_tap_interval } -local ges_two_finger_tap_duration = G_reader_settings:readSetting("ges_two_finger_tap_duration") or TWO_FINGER_TAP_DURATION -ges_two_finger_tap_duration = TimeVal:new{ usec = ges_two_finger_tap_duration } -local ges_hold_interval = G_reader_settings:readSetting("ges_hold_interval") or HOLD_INTERVAL -ges_hold_interval = TimeVal:new{ usec = ges_hold_interval } -local ges_swipe_interval = G_reader_settings:readSetting("ges_swipe_interval") or SWIPE_INTERVAL -ges_swipe_interval = TimeVal:new{ usec = ges_swipe_interval } +-- default values (time parameters are in milliseconds (ms)) +local TAP_INTERVAL_MS = 0 +local DOUBLE_TAP_INTERVAL_MS = 300 +local TWO_FINGER_TAP_DURATION_MS = 300 +local HOLD_INTERVAL_MS = 500 +local SWIPE_INTERVAL_MS = 900 local GestureDetector = { -- must be initialized with the Input singleton class input = nil, -- default values (accessed for display by plugins/gestures.koplugin) - TAP_INTERVAL = TAP_INTERVAL, - DOUBLE_TAP_INTERVAL = DOUBLE_TAP_INTERVAL, - TWO_FINGER_TAP_DURATION = TWO_FINGER_TAP_DURATION, - HOLD_INTERVAL = HOLD_INTERVAL, - SWIPE_INTERVAL = SWIPE_INTERVAL, + TAP_INTERVAL_MS = TAP_INTERVAL_MS, + DOUBLE_TAP_INTERVAL_MS = DOUBLE_TAP_INTERVAL_MS, + TWO_FINGER_TAP_DURATION_MS = TWO_FINGER_TAP_DURATION_MS, + HOLD_INTERVAL_MS = HOLD_INTERVAL_MS, + SWIPE_INTERVAL_MS = SWIPE_INTERVAL_MS, -- pinch/spread direction table DIRECTION_TABLE = { east = "horizontal", @@ -106,6 +95,14 @@ local GestureDetector = { last_taps = {}, -- for timestamp clocksource detection clock_id = nil, + -- current values + ges_tap_interval = time.ms(G_reader_settings:readSetting("ges_tap_interval_ms") or TAP_INTERVAL_MS), + ges_double_tap_interval = time.ms(G_reader_settings:readSetting("ges_double_tap_interval_ms") + or DOUBLE_TAP_INTERVAL_MS), + ges_two_finger_tap_duration = time.ms(G_reader_settings:readSetting("ges_two_finger_tap_duration_ms") + or TWO_FINGER_TAP_DURATION_MS), + ges_hold_interval = time.ms(G_reader_settings:readSetting("ges_hold_interval_ms") or HOLD_INTERVAL_MS), + ges_swipe_interval = time.ms(G_reader_settings:readSetting("ges_swipe_interval_ms") or SWIPE_INTERVAL_MS), } function GestureDetector:new(o) @@ -167,38 +164,38 @@ tap2 is the later tap function GestureDetector:isTapBounce(tap1, tap2, interval) -- NOTE: If time went backwards, make the delta infinite to avoid misdetections, -- as we can no longer compute a sensible value... - local tv_diff = tap2.timev - tap1.timev - if not tv_diff:isPositive() then - tv_diff = TimeVal.huge + local time_diff = tap2.timev - tap1.timev + if time_diff < 0 then + time_diff = time.huge end return ( math.abs(tap1.x - tap2.x) < self.SINGLE_TAP_BOUNCE_DISTANCE and math.abs(tap1.y - tap2.y) < self.SINGLE_TAP_BOUNCE_DISTANCE and - tv_diff < interval + time_diff < interval ) end function GestureDetector:isDoubleTap(tap1, tap2) - local tv_diff = tap2.timev - tap1.timev - if not tv_diff:isPositive() then - tv_diff = TimeVal.huge + local time_diff = tap2.timev - tap1.timev + if time_diff < 0 then + time_diff = time.huge end return ( math.abs(tap1.x - tap2.x) < self.DOUBLE_TAP_DISTANCE and math.abs(tap1.y - tap2.y) < self.DOUBLE_TAP_DISTANCE and - tv_diff < ges_double_tap_interval + time_diff < self.ges_double_tap_interval ) end --- Takes TimeVals as input, not a tev -function GestureDetector:isHold(t1, t2) - local tv_diff = t2 - t1 - if not tv_diff:isPositive() then - tv_diff = TimeVal.zero +-- Takes times as input, not a tev +function GestureDetector:isHold(time1, time2) + local time_diff = time2 - time1 + if time_diff < 0 then + time_diff = 0 end -- NOTE: We cheat by not checking a distance because we're only checking that in tapState, -- which already ensures a stationary finger, by elimination ;). - return tv_diff >= ges_hold_interval + return time_diff >= self.ges_hold_interval end function GestureDetector:isTwoFingerTap() @@ -212,21 +209,21 @@ function GestureDetector:isTwoFingerTap() local x_diff1 = math.abs(self.last_tevs[s2].x - self.first_tevs[s2].x) local y_diff0 = math.abs(self.last_tevs[s1].y - self.first_tevs[s1].y) local y_diff1 = math.abs(self.last_tevs[s2].y - self.first_tevs[s2].y) - local tv_diff0 = self.last_tevs[s1].timev - self.first_tevs[s1].timev - if not tv_diff0:isPositive() then - tv_diff0 = TimeVal.huge + local time_diff0 = self.last_tevs[s1].timev - self.first_tevs[s1].timev + if time_diff0 < 0 then + time_diff0 = time.huge end - local tv_diff1 = self.last_tevs[s2].timev - self.first_tevs[s2].timev - if not tv_diff1:isPositive() then - tv_diff1 = TimeVal.huge + local time_diff1 = self.last_tevs[s2].timev - self.first_tevs[s2].timev + if time_diff1 < 0 then + time_diff1 = time.huge end return ( x_diff0 < self.TWO_FINGER_TAP_REGION and x_diff1 < self.TWO_FINGER_TAP_REGION and y_diff0 < self.TWO_FINGER_TAP_REGION and y_diff1 < self.TWO_FINGER_TAP_REGION and - tv_diff0 < ges_two_finger_tap_duration and - tv_diff1 < ges_two_finger_tap_duration + time_diff0 < self.ges_two_finger_tap_duration and + time_diff1 < self.ges_two_finger_tap_duration ) end @@ -264,11 +261,11 @@ end function GestureDetector:isSwipe(slot) if not self.first_tevs[slot] or not self.last_tevs[slot] then return end - local tv_diff = self.last_tevs[slot].timev - self.first_tevs[slot].timev - if not tv_diff:isPositive() then - tv_diff = TimeVal.huge + local time_diff = self.last_tevs[slot].timev - self.first_tevs[slot].timev + if time_diff < 0 then + time_diff = time.huge end - if tv_diff < ges_swipe_interval then + if time_diff < self.ges_swipe_interval then local x_diff = self.last_tevs[slot].x - self.first_tevs[slot].x local y_diff = self.last_tevs[slot].y - self.first_tevs[slot].y if x_diff ~= 0 or y_diff ~= 0 then @@ -307,34 +304,6 @@ function GestureDetector:clearState(slot) self.input:clearTimeout(slot, "hold") end -function GestureDetector:setNewInterval(type, interval) - if type == "ges_tap_interval" then - ges_tap_interval = TimeVal:new{ usec = interval } - elseif type == "ges_double_tap_interval" then - ges_double_tap_interval = TimeVal:new{ usec = interval } - elseif type == "ges_two_finger_tap_duration" then - ges_two_finger_tap_duration = TimeVal:new{ usec = interval } - elseif type == "ges_hold_interval" then - ges_hold_interval = TimeVal:new{ usec = interval } - elseif type == "ges_swipe_interval" then - ges_swipe_interval = TimeVal:new{ usec = interval } - end -end - -function GestureDetector:getInterval(type) - if type == "ges_tap_interval" then - return ges_tap_interval:tousecs() - elseif type == "ges_double_tap_interval" then - return ges_double_tap_interval:tousecs() - elseif type == "ges_two_finger_tap_duration" then - return ges_two_finger_tap_duration:tousecs() - elseif type == "ges_hold_interval" then - return ges_hold_interval:tousecs() - elseif type == "ges_swipe_interval" then - return ges_swipe_interval:tousecs() - end -end - function GestureDetector:clearStates() for k, _ in pairs(self.states) do self:clearState(k) @@ -371,10 +340,10 @@ Attempts to figure out which clock source tap events are using... function GestureDetector:probeClockSource(timev) -- We'll check if that timestamp is +/- 2.5s away from the three potential clock sources supported by evdev. -- We have bigger issues than this if we're parsing events more than 3s late ;). - local threshold = TimeVal:new{ sec = 2, usec = 500000 } + local threshold = time.s(2) + time.ms(500) -- Start w/ REALTIME, because it's the easiest to detect ;). - local realtime = TimeVal:realtime_coarse() + local realtime = time.realtime_coarse() -- clock-threshold <= timev <= clock+threshold if timev >= realtime - threshold and timev <= realtime + threshold then self.clock_id = C.CLOCK_REALTIME @@ -383,7 +352,7 @@ function GestureDetector:probeClockSource(timev) end -- Then MONOTONIC, as it's (hopefully) more common than BOOTTIME (and also guaranteed to be an usable clock source) - local monotonic = TimeVal:monotonic_coarse() + local monotonic = time.monotonic_coarse() if timev >= monotonic - threshold and timev <= monotonic + threshold then self.clock_id = C.CLOCK_MONOTONIC logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_MONOTONIC") @@ -391,9 +360,9 @@ function GestureDetector:probeClockSource(timev) end -- Finally, BOOTTIME - local boottime = TimeVal:boottime() + local boottime = time.boottime() -- NOTE: It was implemented in Linux 2.6.39, so, reject 0, which would mean it's unsupported... - if not boottime:isZero() and timev >= boottime - threshold and timev <= boottime + threshold then + if not boottime == 0 and timev >= boottime - threshold and timev <= boottime + threshold then self.clock_id = C.CLOCK_BOOTTIME logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_BOOTTIME") return @@ -403,10 +372,10 @@ function GestureDetector:probeClockSource(timev) self.clock_id = -1 logger.info("GestureDetector:probeClockSource: Touch event clock source detection was inconclusive") -- Print all all the gory details in debug mode when this happens... - logger.dbg("Input frame :", timev:tonumber()) - logger.dbg("CLOCK_REALTIME :", realtime:tonumber()) - logger.dbg("CLOCK_MONOTONIC:", monotonic:tonumber()) - logger.dbg("CLOCK_BOOTTIME :", boottime:tonumber()) + logger.dbg("Input frame :", time.format_time(timev)) + logger.dbg("CLOCK_REALTIME :", time.format_time(realtime)) + logger.dbg("CLOCK_MONOTONIC:", time.format_time(monotonic)) + logger.dbg("CLOCK_BOOTTIME :", time.format_time(boottime)) end function GestureDetector:getClockSource() @@ -498,10 +467,10 @@ function GestureDetector:handleDoubleTap(tev) } -- Tap interval / bounce detection may be tweaked by widget (i.e. VirtualKeyboard) - local tap_interval = self.input.tap_interval_override or ges_tap_interval + local tap_interval = self.input.tap_interval_override or self.ges_tap_interval -- We do tap bounce detection even when double tap is enabled (so, double tap -- is triggered when: ges_tap_interval <= delay < ges_double_tap_interval) - if not tap_interval:isZero() and self.last_taps[slot] ~= nil and self:isTapBounce(self.last_taps[slot], cur_tap, tap_interval) then + if tap_interval ~= 0 and self.last_taps[slot] ~= nil and self:isTapBounce(self.last_taps[slot], cur_tap, tap_interval) then logger.dbg("tap bounce detected in slot", slot, ": ignored") -- Simply ignore it, and clear state as this is the end of a touch event -- (this doesn't clear self.last_taps[slot], so a 3rd tap can be detected @@ -547,7 +516,7 @@ function GestureDetector:handleDoubleTap(tev) logger.dbg("single tap detected in slot", slot, ges_ev.pos) return ges_ev end - end, tev.timev, ges_double_tap_interval) + end, tev.timev, self.ges_double_tap_interval) -- we are already at the end of touch event -- so reset the state self:clearState(slot) @@ -573,7 +542,7 @@ function GestureDetector:handleNonTap(tev) self.pending_hold_timer[slot] = nil return self:switchState("holdState", tev, true) end - end, tev.timev, ges_hold_interval) + end, tev.timev, self.ges_hold_interval) return { ges = "touch", pos = Geom:new{ diff --git a/frontend/device/input.lua b/frontend/device/input.lua index 8b2f0479b..a70cbcee1 100644 --- a/frontend/device/input.lua +++ b/frontend/device/input.lua @@ -7,10 +7,10 @@ local DEBUG = require("dbg") local Event = require("ui/event") local GestureDetector = require("device/gesturedetector") local Key = require("device/key") -local TimeVal = require("ui/timeval") local framebuffer = require("ffi/framebuffer") local input = require("ffi/input") local logger = require("logger") +local time = require("ui/time") local _ = require("gettext") -- We're going to need a few constants... @@ -372,7 +372,7 @@ function Input:setTimeout(slot, ges, cb, origin, delay) if input.setTimer then -- If GestureDetector's clock source probing was inconclusive, do this on the UI timescale instead. if clock_id == -1 then - deadline = TimeVal:now() + delay + deadline = time.now() + delay clock_id = C.CLOCK_MONOTONIC else deadline = origin + delay @@ -380,7 +380,8 @@ function Input:setTimeout(slot, ges, cb, origin, delay) -- What this does is essentially to ask the kernel to wake us up when the timer expires, -- instead of ensuring that ourselves via a polling timeout. -- This ensures perfect accuracy, and allows it to be computed in the event's own timescale. - timerfd = input.setTimer(clock_id, deadline.sec, deadline.usec) + local sec, usec = time.split_s_us(deadline) + timerfd = input.setTimer(clock_id, sec, usec) end if timerfd then -- It worked, tweak the table a bit to make it clear the deadline will be handled by the kernel @@ -395,7 +396,7 @@ function Input:setTimeout(slot, ges, cb, origin, delay) else -- Otherwise, fudge it by using a current timestamp in the UI's timescale (MONOTONIC). -- This isn't the end of the world in practice (c.f., #7415). - deadline = TimeVal:now() + delay + deadline = time.now() + delay end item.deadline = deadline end @@ -709,10 +710,8 @@ function Input:handleTouchEv(ev) end elseif ev.type == C.EV_SYN then if ev.code == C.SYN_REPORT then - -- Promote our event's time table to a real TimeVal - setmetatable(ev.time, TimeVal) for _, MTSlot in ipairs(self.MTSlots) do - self:setMtSlot(MTSlot.slot, "timev", ev.time) + self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time)) end -- feed ev in all slots to state machine local touch_ges = self.gesture_detector:feedEvent(self.MTSlots) @@ -773,9 +772,8 @@ function Input:handleTouchEvPhoenix(ev) end elseif ev.type == C.EV_SYN then if ev.code == C.SYN_REPORT then - setmetatable(ev.time, TimeVal) for _, MTSlot in ipairs(self.MTSlots) do - self:setMtSlot(MTSlot.slot, "timev", ev.time) + self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time)) end -- feed ev in all slots to state machine local touch_ges = self.gesture_detector:feedEvent(self.MTSlots) @@ -809,9 +807,8 @@ function Input:handleTouchEvLegacy(ev) end elseif ev.type == C.EV_SYN then if ev.code == C.SYN_REPORT then - setmetatable(ev.time, TimeVal) for _, MTSlot in ipairs(self.MTSlots) do - self:setMtSlot(MTSlot.slot, "timev", ev.time) + self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time)) end -- feed ev in all slots to state machine @@ -1022,8 +1019,8 @@ end --- Main event handling. --- `now` corresponds to UIManager:getTime() (a TimeVal), and it's just been updated by UIManager. --- `deadline` (a TimeVal) is the absolute deadline imposed by UIManager:handleInput() (a.k.a., our main event loop ^^): +-- `now` corresponds to UIManager:getTime() (an fts time), and it's just been updated by UIManager. +-- `deadline` (an fts time) is the absolute deadline imposed by UIManager:handleInput() (a.k.a., our main event loop ^^): -- it's either nil (meaning block forever waiting for input), or the earliest UIManager deadline (in most cases, that's the next scheduled task, -- in much less common cases, that's the earliest of UIManager.INPUT_TIMEOUT (currently, only KOSync ever sets it) or UIManager.ZMQ_TIMEOUT if there are pending ZMQs). function Input:waitEvent(now, deadline) @@ -1073,18 +1070,19 @@ function Input:waitEvent(now, deadline) if poll_deadline then -- If we haven't hit that deadline yet, poll until it expires, otherwise, -- have select return immediately so that we trip a timeout. - now = now or TimeVal:now() + now = now or time.now() if poll_deadline > now then -- Deadline hasn't been blown yet, honor it. poll_timeout = poll_deadline - now else -- We've already blown the deadline: make select return immediately (most likely straight to timeout) - poll_timeout = TimeVal.zero + poll_timeout = 0 end end local timerfd - ok, ev, timerfd = input.waitForEvent(poll_timeout and poll_timeout.sec, poll_timeout and poll_timeout.usec) + local sec, usec = time.split_s_us(poll_timeout) + ok, ev, timerfd = input.waitForEvent(sec, usec) -- We got an actual input event, go and process it if ok then break end @@ -1102,7 +1100,7 @@ function Input:waitEvent(now, deadline) -- We're only guaranteed to have blown the timer's deadline -- when our actual select deadline *was* the timer's! consume_callback = true - elseif TimeVal:now() >= self.timer_callbacks[1].deadline then + elseif time.now() >= self.timer_callbacks[1].deadline then -- But if it was a task deadline instead, we to have to check the timer's against the current time, -- to double-check whether we blew it or not. consume_callback = true @@ -1144,17 +1142,18 @@ function Input:waitEvent(now, deadline) -- If UIManager put us on deadline, enforce it, otherwise, block forever. if deadline then -- Convert that absolute deadline to value relative to *now*, as we may loop multiple times between UI ticks. - now = now or TimeVal:now() + now = now or time.now() if deadline > now then -- Deadline hasn't been blown yet, honor it. poll_timeout = deadline - now else -- Deadline has been blown: make select return immediately. - poll_timeout = TimeVal.zero + poll_timeout = 0 end end - ok, ev = input.waitForEvent(poll_timeout and poll_timeout.sec, poll_timeout and poll_timeout.usec) + local sec, usec = time.split_s_us(poll_timeout) + ok, ev = input.waitForEvent(sec, usec) end -- if #timer_callbacks > 0 -- Handle errors diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index d9ccc4e64..2b75a6137 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -824,14 +824,14 @@ function Kobo:standby(max_duration) self.wakeup_mgr:addTask(max_duration, standby_alarm) end - local TimeVal = require("ui/timeval") + local time = require("ui/time") logger.info("Kobo standby: asking to enter standby . . .") - local standby_time_tv = TimeVal:boottime_or_realtime_coarse() + local standby_time = time.boottime_or_realtime_coarse() local ret = writeToSys("standby", "/sys/power/state") - self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv - self.total_standby_tv = self.total_standby_tv + self.last_standby_tv + self.last_standby_time = time.boottime_or_realtime_coarse() - standby_time + self.total_standby_time = self.total_standby_time + self.last_standby_time if ret then logger.info("Kobo standby: zZz zZz zZz zZz... And woke up!") @@ -925,16 +925,16 @@ function Kobo:suspend() end --]] - local TimeVal = require("ui/timeval") + local time = require("ui/time") logger.info("Kobo suspend: asking for a suspend to RAM . . .") - local suspend_time_tv = TimeVal:boottime_or_realtime_coarse() + local suspend_time = time.boottime_or_realtime_coarse() ret = writeToSys("mem", "/sys/power/state") -- NOTE: At this point, we *should* be in suspend to RAM, as such, -- execution should only resume on wakeup... - self.last_suspend_tv = TimeVal:boottime_or_realtime_coarse() - suspend_time_tv - self.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv + self.last_suspend_time = time.boottime_or_realtime_coarse() - suspend_time + self.total_suspend_time = self.total_suspend_time + self.last_suspend_time if ret then logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!") diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua index 1a654d026..25a00c700 100644 --- a/frontend/device/remarkable/device.lua +++ b/frontend/device/remarkable/device.lua @@ -1,7 +1,7 @@ local Generic = require("device/generic/device") -- <= look at this file! -local TimeVal = require("ui/timeval") local PluginShare = require("pluginshare") local logger = require("logger") +local time = require("ui/time") local ffi = require("ffi") local C = ffi.C require("ffi/linux_input_h") @@ -95,7 +95,7 @@ function Remarkable2:adjustTouchEvent(ev, by) -- Inject CLOCK_MONOTONIC timestamps at the end of every input frame in order to have consistent gesture detection across input devices. -- c.f., #7536 if ev.type == C.EV_SYN and ev.code == C.SYN_REPORT then - ev.time = TimeVal:now() + ev.time = time.now() end end diff --git a/frontend/device/sdl/device.lua b/frontend/device/sdl/device.lua index 94dce441a..cef65e087 100644 --- a/frontend/device/sdl/device.lua +++ b/frontend/device/sdl/device.lua @@ -168,7 +168,6 @@ function Device:init() event_map = require("device/sdl/event_map_sdl2"), handleSdlEv = function(device_input, ev) local Geom = require("ui/geometry") - local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") -- SDL events can remain cdata but are almost completely transparent @@ -192,8 +191,6 @@ function Device:init() w = 0, h = 0, } - setmetatable(ev.time, TimeVal) - local fake_ges = { ges = "pan", distance = 200, diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index f72d9e51c..68fef05c3 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -6,13 +6,13 @@ local FontList = require("fontlist") local Geom = require("ui/geometry") local RenderImage = require("ui/renderimage") local Screen = require("device").screen -local TimeVal = require("ui/timeval") local buffer = require("string.buffer") local ffi = require("ffi") local C = ffi.C local lfs = require("libs/libkoreader-lfs") local logger = require("logger") local lru = require("ffi/lru") +local time = require("ui/time") -- engine can be initialized only once, on first document opened local engine_initialized = false @@ -694,16 +694,16 @@ function CreDocument:drawCurrentView(target, x, y, rect, pos) -- We also honor the current smooth scaling setting, -- as well as the global SW dithering setting. - --local start_tv = TimeVal:now() + --local start_time = time.now() self._drawn_images_count, self._drawn_images_surface_ratio = self._document:drawCurrentPage(self.buffer, self.render_color, Screen.night_mode and self._nightmode_images, self._smooth_scaling, Screen.sw_dithering) - --local end_tv = TimeVal:now() - --print(string.format("CreDocument:drawCurrentView: Rendering took %9.3f ms", (end_tv - start_tv):tomsecs())) + --local end_time = time.now() + --print(string.format("CreDocument:drawCurrentView: Rendering took %9.3f ms", time.to_ms(end_time - start_time)) - --start_tv = TimeVal:now() + --start = time.now() target:blitFrom(self.buffer, x, y, 0, 0, rect.w, rect.h) - --end_tv = TimeVal:now() - --print(string.format("CreDocument:drawCurrentView: Blitting took %9.3f ms", (end_tv - start_tv):tomsecs())) + --end_time = time.now() + --print(string.format("CreDocument:drawCurrentView: Blitting took %9.3f ms", time.to_ms(end_time - start_time)) end function CreDocument:drawCurrentViewByPos(target, x, y, rect, pos) @@ -1457,10 +1457,10 @@ function CreDocument:setupCallCache() -- cache statistics self._call_cache_stats = {} now = function() - return TimeVal:now() + return time.now() end addStatMiss = function(name, starttime, not_cached) - local duration = TimeVal:getDuration(starttime) + local duration = time.since(starttime) if not self._call_cache_stats[name] then self._call_cache_stats[name] = {0, 0.0, 1, duration, not_cached} else @@ -1470,7 +1470,7 @@ function CreDocument:setupCallCache() end end addStatHit = function(name, starttime) - local duration = TimeVal:getDuration(starttime) + local duration = time.since(starttime) if not self._call_cache_stats[name] then self._call_cache_stats[name] = {1, duration, 0, 0.0} else diff --git a/frontend/ui/data/keyboardlayouts/ja_keyboard.lua b/frontend/ui/data/keyboardlayouts/ja_keyboard.lua index 73e2e55ae..f67b93fea 100644 --- a/frontend/ui/data/keyboardlayouts/ja_keyboard.lua +++ b/frontend/ui/data/keyboardlayouts/ja_keyboard.lua @@ -10,16 +10,16 @@ -- able to get there easily. -------- -local TimeVal = require("ui/timeval") local logger = require("logger") local util = require("util") +local time = require("ui/time") local _ = require("gettext") local N_ = _.ngettext local T = require("ffi/util").template local K = require("frontend/ui/data/keyboardlayouts/ja_keyboard_keys") -local DEFAULT_KEITAI_TAP_INTERVAL = 2 +local DEFAULT_KEITAI_TAP_INTERVAL_S = 2 -- "Keitai input" is an input mode similar to T9 mobile input, where you tap a -- key to cycle through several candidate characters. The tap interval is how @@ -28,16 +28,16 @@ local DEFAULT_KEITAI_TAP_INTERVAL = 2 -- information. local function getKeitaiTapInterval() - return G_reader_settings:readSetting("keyboard_japanese_keitai_tap_interval") or DEFAULT_KEITAI_TAP_INTERVAL + return time.s(G_reader_settings:readSetting("keyboard_japanese_keitai_tap_interval", DEFAULT_KEITAI_TAP_INTERVAL_S)) end local function setKeitaiTapInterval(interval) - G_reader_settings:saveSetting("keyboard_japanese_keitai_tap_interval", interval) + G_reader_settings:saveSetting("keyboard_japanese_keitai_tap_interval", time.to_s(interval)) end local function exitKeitaiMode(inputbox) logger.dbg("ja_kbd: clearing keitai window last tap tv") - inputbox._ja_last_tap_tv = nil + inputbox._ja_last_tap_time = nil end local function wrappedAddChars(inputbox, char) @@ -48,10 +48,10 @@ local function wrappedAddChars(inputbox, char) -- For keitai buttons, are we still in the tap interval? local within_tap_window if keitai_cycle then - if inputbox._ja_last_tap_tv then - within_tap_window = TimeVal:getDuration(inputbox._ja_last_tap_tv) < getKeitaiTapInterval() + if inputbox._ja_last_tap_time then + within_tap_window = time.since(inputbox._ja_last_tap_time) < getKeitaiTapInterval() end - inputbox._ja_last_tap_tv = TimeVal:now() + inputbox._ja_last_tap_time = time.now() else -- This is a non-keitai or non-tap key, so break out of keitai window. exitKeitaiMode(inputbox) @@ -113,7 +113,7 @@ local function wrapInputBox(inputbox) for _, wrapper in ipairs(wrappers) do wrapper:revert() end - inputbox._ja_last_tap_tv = nil + inputbox._ja_last_tap_time = nil inputbox._ja_wrapped = nil end end @@ -127,7 +127,7 @@ local function genMenuItems(self) local interval = getKeitaiTapInterval() if interval ~= 0 then -- @translators Keitai input is a kind of Japanese keyboard input mode (similar to T9 keypad input). See for more information. - return T(N_("Keitai tap interval: %1 second", "Keitai tap interval: %1 seconds", interval), interval) + return T(N_("Keitai tap interval: %1 second", "Keitai tap interval: %1 seconds", time.to_s(interval)), time.to_s(interval)) else -- @translators Flick and keitai are kinds of Japanese keyboard input modes. See for more information. return _("Keitai input: disabled (flick-only input)") @@ -146,14 +146,14 @@ How long to wait (in seconds) for the next tap when in keitai input mode before If set to 0, keitai input is disabled entirely and only flick input can be used.]]), width = math.floor(Screen:getWidth() * 0.75), - value = getKeitaiTapInterval(), + value = time.to_s(getKeitaiTapInterval()), value_min = 0, value_max = 10, value_step = 1, ok_text = _("Set interval"), - default_value = DEFAULT_KEITAI_TAP_INTERVAL, + default_value = DEFAULT_KEITAI_TAP_INTERVAL_S, callback = function(spin) - setKeitaiTapInterval(spin.value) + setKeitaiTapInterval(time.s(spin.value)) if touchmenu_instance then touchmenu_instance:updateItems() end end, } diff --git a/frontend/ui/data/onetime_migration.lua b/frontend/ui/data/onetime_migration.lua index 12fd81359..fd38369b8 100644 --- a/frontend/ui/data/onetime_migration.lua +++ b/frontend/ui/data/onetime_migration.lua @@ -7,7 +7,7 @@ local lfs = require("libs/libkoreader-lfs") local logger = require("logger") -- Date at which the last migration snippet was added -local CURRENT_MIGRATION_DATE = 20220205 +local CURRENT_MIGRATION_DATE = 20220426 -- Retrieve the date of the previous migration, if any local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0) @@ -366,5 +366,27 @@ if last_migration_date < 20220205 then end end +-- Rename several time storing settings and shift their value to the new meaning +if last_migration_date < 20220426 then + local function migrateSettingsName(old, new, factor) + factor = factor or 1 + if G_reader_settings:readSetting(old) then + local value = math.floor(G_reader_settings:readSetting(old) * factor) + G_reader_settings:saveSetting(new, value) + G_reader_settings:delSetting(old) + end + end + migrateSettingsName("ges_tap_interval", "ges_tap_interval_ms", 1e-3) + migrateSettingsName("ges_double_tap_interval", "ges_double_tap_interval_ms", 1e-3) + migrateSettingsName("ges_two_finger_tap_duration", "ges_two_finger_tap_duration_ms", 1e-3) + migrateSettingsName("ges_hold_interval", "ges_hold_interval_ms", 1e-3) + migrateSettingsName("ges_swipe_interval", "ges_swipe_interval_ms", 1e-3) + migrateSettingsName("ges_tap_interval_on_keyboard", "ges_tap_interval_on_keyboard_ms", 1e-3) + + migrateSettingsName("device_status_battery_interval", "device_status_battery_interval_minutes") + migrateSettingsName("device_status_memory_interval", "device_status_memory_interval_minutes") +end + + -- We're done, store the current migration date G_reader_settings:saveSetting("last_migration_date", CURRENT_MIGRATION_DATE) diff --git a/frontend/ui/gesturerange.lua b/frontend/ui/gesturerange.lua index 881b1d6eb..a42fe062a 100644 --- a/frontend/ui/gesturerange.lua +++ b/frontend/ui/gesturerange.lua @@ -1,4 +1,4 @@ -local TimeVal = require("ui/timeval") +local time = require("ui/time") local GestureRange = { -- gesture matching type @@ -43,8 +43,8 @@ function GestureRange:match(gs) -- This field sets up rate-limiting (in matches per second). -- It's mostly useful for e-Ink devices with less powerful CPUs -- and screens that cannot handle the amount of gesture events that would otherwise be generated. - local last_time = self.last_time or TimeVal.zero - if gs.time - last_time > TimeVal:new{ usec = 1000000 / self.rate } then + local last_time = self.last_time or 0 + if gs.time - last_time > time.s(1 / self.rate) then self.last_time = gs.time else return false diff --git a/frontend/ui/network/networklistener.lua b/frontend/ui/network/networklistener.lua index 8850348d6..f347615a0 100644 --- a/frontend/ui/network/networklistener.lua +++ b/frontend/ui/network/networklistener.lua @@ -123,8 +123,8 @@ function NetworkListener:_unscheduleActivityCheck() if self._last_tx_packets then self._last_tx_packets = nil end - if self._activity_check_delay then - self._activity_check_delay = nil + if self._activity_check_delay_seconds then + self._activity_check_delay_seconds = nil end end @@ -135,8 +135,8 @@ function NetworkListener:_scheduleActivityCheck() local tx_packets = NetworkListener:_getTxPackets() if self._last_tx_packets and tx_packets then -- Compute noise threshold based on the current delay - local delay = self._activity_check_delay or default_network_timeout_seconds - local noise_threshold = delay / default_network_timeout_seconds * network_activity_noise_margin + local delay_seconds = self._activity_check_delay_seconds or default_network_timeout_seconds + local noise_threshold = delay_seconds / default_network_timeout_seconds * network_activity_noise_margin local delta = tx_packets - self._last_tx_packets -- If there was no meaningful activity (+/- a couple packets), kill the Wi-Fi if delta <= noise_threshold then @@ -161,19 +161,19 @@ function NetworkListener:_scheduleActivityCheck() self._last_tx_packets = tx_packets -- If it's already been scheduled, increase the delay until we hit the ceiling - if self._activity_check_delay then - self._activity_check_delay = self._activity_check_delay + default_network_timeout_seconds + if self._activity_check_delay_seconds then + self._activity_check_delay_seconds = self._activity_check_delay_seconds + default_network_timeout_seconds - if self._activity_check_delay > max_network_timeout_seconds then - self._activity_check_delay = max_network_timeout_seconds + if self._activity_check_delay_seconds > max_network_timeout_seconds then + self._activity_check_delay_seconds = max_network_timeout_seconds end else - self._activity_check_delay = default_network_timeout_seconds + self._activity_check_delay_seconds = default_network_timeout_seconds end - UIManager:scheduleIn(self._activity_check_delay, self._scheduleActivityCheck, self) + UIManager:scheduleIn(self._activity_check_delay_seconds, self._scheduleActivityCheck, self) self._activity_check_scheduled = true - logger.dbg("NetworkListener: network activity check scheduled in", self._activity_check_delay, "seconds") + logger.dbg("NetworkListener: network activity check scheduled in", self._activity_check_delay_seconds, "seconds") end function NetworkListener:onNetworkConnected() @@ -211,5 +211,4 @@ function NetworkListener:onSuspend() self:onNetworkDisconnected() end - return NetworkListener diff --git a/frontend/ui/time.lua b/frontend/ui/time.lua new file mode 100644 index 000000000..e794bdd6f --- /dev/null +++ b/frontend/ui/time.lua @@ -0,0 +1,307 @@ +--[[-- +A runtime optimized module to compare and do simple arithmetics with fixed point time values (which are called fts in here). + +Also implements functions to retrieve time from various system clocks (monotonic, monotonic_coarse, realtime, realtime_coarse, boottime ...). + +**Encode:** + +Don't store a numerical constant in an fts encoded time. Use the functions provided here! + +To convert real world units to an fts, you can use the following functions: time.s(seconds), time.ms(milliseconds), time.us(microseconds). + +You can calculate an fts encoded time of 3 s with `time.s(3)`. + +Special values: `0` can be used for a zero time and `time.huge` can be used for the longest possible time. + +Beware of float encoding precision, though. For instance, take 2.1s: 2.1 cannot be encoded with full precision, so time.s(2.1) would be slightly inaccurate. +(For small values (under 10 secs) the error will be ±1µs, for values below a minute the error will be below ±2µs, for values below an hour the error will be ±100µs.) + +When full precision is necessary, use `time.s(2) + time.ms(100)` or `time.s(2) + time.us(100000)` instead. + +(For more information about floating-point-representation see: https://stackoverflow.com/questions/3448777/how-to-represent-0-1-in-floating-point-arithmetic-and-decimal) + +**Decode:** + +You can get the number of seconds in an fts encoded time with `time.to_s(time_fts)`. + +You can get the number of milliseconds in an fts encoded time with `time.to_ms(time_fts)`. + +You can get the number of microseconds in an fts encoded time with `time.to_us(time_fts)`. + +Please be aware, that `time.to_number` is the same as a `time.to_s` with a precision of four decimal places. + +**Supported calculations:** + +You can add and subtract all fts encoded times, without any problems. + +You can multiply or divide fts encoded times by numerical constants. So if you need the half of a time, `time_fts/2` is correct. + +A division of two fts encoded times would give you a number. (e.g., `time.s(2.5)/time.s(0.5)` equals `5`). + +The functions `math.abs()`, `math.min()`, `math.max()` and `math.huge` will work as expected. + +Comparisons (`>`, `>=`, `==`, `<`, `<=` and `~=`) of two fts encoded times work as expected. + +If you want a duration form a given time_fts to *now*, `time.since(time_fts)` as a shortcut (or simply use `fts.now - time_fts`) will return an fts encoded time. If you need milliseconds use `time.to_ms(time.since(time_fts))`. + +**Unsupported calculations:** + +Don't add a numerical constant to an fts time (in the best case, the numerical constant is interpreted as µs). + +Don't multiply two fts_encoded times (the position of the comma is wrong). + +But please be aware that _all other not explicitly supported_ math on fts encoded times (`math.xxx()`) won't work as expected. (If you really, really need that, you have to shift the position of the comma yourself!) + +**Background:** +Numbers in Lua are double float which have a mantissa (precision) of 53 bit (plus sign + exponent) +We won't use the exponent here. + +So we can store 2^53 = 9.0072E15 different values. If we use the lower 6 digits for µs, we can store +up to 9.0072E9 seconds. + +A year has 365.25*24*3600 = 3.15576E7 s, so we can store up to 285 years (9.0072E9/3.15576E7) with µs precision. + +The module has been tested with the fixed point comma at 10^6 (other values might work, but are not really tested). + +**Recommendations:** +If the name of a variable implies a time (now, when, until, xxxdeadline, xxxtime, getElapsedTimeSinceBoot, lastxxxtimexxx, ...) we assume this value to be a time (fts encoded). + +Other objects which are times (like `last_tap`, `tap_interval_override`, ...) shall be renamed to something like `last_tap_time` (so to make it clear that they are fts encoded). + +All other time variables (a handful) get the appropriate suffix `_ms`, `_us`, `_s` (`_m`, `_h`, `_d`) denoting their status as plain Lua numbers and their resolution. + +@module time + +@usage + local time = require("ui/time") + + local start_time = time.now() + -- Do some stuff. + -- You can add and subtract `fts times` objects. + local duration = time.now() - start.fts + -- And convert that object to various more human-readable formats, e.g., + print(string.format("Stuff took %.3fms", time.to_ms(duration))) + + local offset = time.s(100) + print(string.format("Stuff plus 100s took %.3fms", time.to_ms(duration + offset))) +]] + +local ffi = require("ffi") +require("ffi/posix_h") +local logger = require("logger") + +local C = ffi.C + +-- An FTS_PRECISION of 1e6 will give us a µs precision. +local FTS_PRECISION = 1e6 + +local S2FTS = FTS_PRECISION +local MS2FTS = FTS_PRECISION / 1e3 +local US2FTS = FTS_PRECISION / 1e6 +local NS2FTS = FTS_PRECISION / 1e9 + +local FTS2S = 1 / S2FTS +local FTS2MS = 1 / MS2FTS +local FTS2US = 1 / US2FTS + +-- Fixed point time +local time = {} + +--- Sometimes we need a very large time +time.huge = math.huge + +--- Creates a time (fts) from a number in seconds +function time.s(seconds) + return math.floor(seconds * S2FTS) +end + +--- Creates a time (fts) from a number in milliseconds +function time.ms(msec) + return math.floor(msec * MS2FTS) +end + +--- Creates a time (fts) from a number in microseconds +function time.us(usec) + return math.floor(usec * US2FTS) +end + +--- Creates a time (fts) from a structure similar to timeval +function time.timeval(tv) + return tv.sec * S2FTS + tv.usec * US2FTS +end + +--- Converts an fts time to a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places) +function time.to_number(time_fts) + -- Round to 4 decimal places + return math.floor(time.to_s(time_fts) * 10000 + 0.5) / 10000 +end + +--- Converts an fts to a Lua (int) number (resolution: 1µs) +function time.to_s(time_fts) + -- Time in seconds with µs precision (without decimal places) + return time_fts * FTS2S +end + +--[[-- Converts a fts to a Lua (int) number (resolution: 1ms, rounded). + +(Mainly useful when computing a time lapse for benchmarking purposes). +]] +function time.to_ms(time_fts) + -- Time in milliseconds ms (without decimal places) + return math.floor(time_fts * FTS2MS + 0.5) +end + +--- Converts an fts to a Lua (int) number (resolution: 1µs, rounded) +function time.to_us(time_fts) + -- Time in microseconds µs (without decimal places) + return math.floor(time_fts * FTS2US + 0.5) +end + +--[[-- Compare a past *MONOTONIC* fts time to *now*, returning the elapsed time between the two. (sec.usecs variant) + +Returns a Lua (decimal) number (sec.usecs, with decimal places) (accurate to the µs) +]] +function time.since(start_time) + -- Time difference + return time.now() - start_time +end + +--- Splits an fts to seconds and microseconds. +-- If argument is nil, returns nil,nil. +function time.split_s_us(time_fts) + if not time_fts then return nil, nil end + local sec = math.floor(time_fts * FTS2S) + local usec = math.floor(time_fts - sec * S2FTS) * FTS2US + -- Seconds and µs + return sec, usec +end + +-- ffi object for C.clock_gettime calls +local timespec = ffi.new("struct timespec") + +-- We prefer CLOCK_MONOTONIC_COARSE if it's available and has a decent resolution, +-- as we generally don't need nano/micro second precision, +-- and it can be more than twice as fast as CLOCK_MONOTONIC/CLOCK_REALTIME/gettimeofday... +local PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC +-- Ditto for REALTIME (for :realtime_coarse only, :realtime uses gettimeofday ;)). +local PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME +-- CLOCK_BOOTTIME is only available on Linux 2.6.39+... +local HAVE_BOOTTIME = false +if ffi.os == "Linux" then + -- Unfortunately, it was only implemented in Linux 2.6.32, and we may run on older kernels than that... + -- So, just probe it to see if we can rely on it. + local probe_ts = ffi.new("struct timespec") + if C.clock_getres(C.CLOCK_MONOTONIC_COARSE, probe_ts) == 0 then + -- Now, it usually has a 1ms resolution on modern x86_64 systems, + -- but it only provides a 10ms resolution on all my armv7 devices :/. + if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then + PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC_COARSE + end + end + logger.dbg("fts: Preferred MONOTONIC clock source is", PREFERRED_MONOTONIC_CLOCKID == C.CLOCK_MONOTONIC_COARSE and "CLOCK_MONOTONIC_COARSE" or "CLOCK_MONOTONIC") + if C.clock_getres(C.CLOCK_REALTIME_COARSE, probe_ts) == 0 then + if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then + PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME_COARSE + end + end + logger.dbg("fts: Preferred REALTIME clock source is", PREFERRED_REALTIME_CLOCKID == C.CLOCK_REALTIME_COARSE and "CLOCK_REALTIME_COARSE" or "CLOCK_REALTIME") + + if C.clock_getres(C.CLOCK_BOOTTIME, probe_ts) == 0 then + HAVE_BOOTTIME = true + end + logger.dbg("fts: BOOTTIME clock source is", HAVE_BOOTTIME and "supported" or "NOT supported") + + probe_ts = nil --luacheck: ignore +end + +--[[-- +Returns an fts time based on the current wall clock time. +(e.g., gettimeofday / clock_gettime(CLOCK_REALTIME). + +This is a simple wrapper around clock_gettime(CLOCK_REALTIME) to get all the niceties of a time. +If you don't need sub-second precision, prefer os.time(). +Which means that, yes, this is a fancier POSIX Epoch ;). + +@usage + local time = require("ui/time") + local fts_start = time.realtime() + -- Do some stuff. + -- You can add and substract fts times + local fts_duration = time.realtime() - fts_start + +@treturn fts fixed point time +]] +function time.realtime() + C.clock_gettime(C.CLOCK_REALTIME, timespec) + -- TIMESPEC_TO_FTS + return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS) +end + +--[[-- +Returns an fts time based on the current value from the system's MONOTONIC clock source. +(e.g., clock_gettime(CLOCK_MONOTONIC).) + +POSIX guarantees that this clock source will *never* go backwards (but it *may* return the same value multiple times). +On Linux, this will not account for time spent with the device in suspend (unlike CLOCK_BOOTTIME). + +@treturn fts fixed point time +]] +function time.monotonic() + C.clock_gettime(C.CLOCK_MONOTONIC, timespec) + -- TIMESPEC_TO_FTS + return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS) +end + +--- Ditto, but w/ CLOCK_MONOTONIC_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_MONOTONIC otherwise). +function time.monotonic_coarse() + C.clock_gettime(PREFERRED_MONOTONIC_CLOCKID, timespec) + -- TIMESPEC_TO_FTS + return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS) +end + +-- Ditto, but w/ CLOCK_REALTIME_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_REALTIME otherwise). +function time.realtime_coarse() + C.clock_gettime(PREFERRED_REALTIME_CLOCKID, timespec) + -- TIMESPEC_TO_FTS + return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS) + end + +--- Since CLOCK_BOOTIME may not be supported, we offer a few aliases with automatic fallbacks to MONOTONIC or REALTIME +if HAVE_BOOTTIME then + --- Ditto, but w/ CLOCK_BOOTTIME (will return an fts time set to 0, 0 if the clock source is unsupported, as it's 2.6.39+) + --- Only use it if you *know* it's going to be supported, otherwise, prefer the four following aliases. + function time.boottime() + C.clock_gettime(C.CLOCK_BOOTTIME, timespec) + -- TIMESPEC_TO_FTS + return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS) + end + + time.boottime_or_monotonic = time.boottime + time.boottime_or_monotonic_coarse = time.boottime + time.boottime_or_realtime = time.boottime + time.boottime_or_realtime_coarse = time.boottime +else + function time.boottime() + logger.warn("fts: Attemped to call boottime on a platform where it's unsupported!") + return 0 + end + + time.boottime_or_monotonic = time.monotonic + time.boottime_or_monotonic_coarse = time.monotonic_coarse + time.boottime_or_realtime = time.realtime + time.boottime_or_realtime_coarse = time.realtime_coarse +end + +--[[-- Alias for `monotonic_coarse`. + +The assumption being anything that requires accurate timestamps expects a monotonic clock source. +This is certainly true for KOReader's UI scheduling. +]] +time.now = time.monotonic_coarse + +--- Converts an fts time to a string (seconds with 6 decimal places) +function time.format_time(time_fts) + return string.format("%.06f", time_fts * FTS2S) +end + +return time diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 1b20e14af..9d58348ec 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -5,11 +5,11 @@ This module manages widgets. local Device = require("device") local Event = require("ui/event") local Geom = require("ui/geometry") -local TimeVal = require("ui/timeval") local dbg = require("dbg") local logger = require("logger") local ffiUtil = require("ffi/util") local util = require("util") +local time = require("ui/time") local _ = require("gettext") local Input = Device.input local Screen = Device.screen @@ -30,7 +30,7 @@ local UIManager = { event_handlers = nil, _running = true, - _now = TimeVal:now(), + _now = time.now(), _window_stack = {}, _task_queue = {}, _task_queue_dirty = false, @@ -523,14 +523,14 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither) end -- schedule an execution task, task queue is in ascendant order -function UIManager:schedule(time, action, ...) +function UIManager:schedule(sched_time, action, ...) local p, s, e = 1, 1, #self._task_queue if e ~= 0 then -- do a binary insert repeat - p = math.floor(s + (e - s) / 2) + p = math.floor((e + s) / 2) -- Not necessary to use (s + (e -s) / 2) here! local p_time = self._task_queue[p].time - if time > p_time then + if sched_time > p_time then if s == e then p = e + 1 break @@ -539,11 +539,11 @@ function UIManager:schedule(time, action, ...) else s = p end - elseif time < p_time then - e = p - if s == e then + elseif sched_time < p_time then + if s == p then break end + e = p else -- for fairness, it's better to make p+1 is strictly less than -- p might want to revisit here in the future @@ -552,7 +552,7 @@ function UIManager:schedule(time, action, ...) until e < s end table.insert(self._task_queue, p, { - time = time, + time = sched_time, action = action, argc = select('#', ...), args = {...}, @@ -560,8 +560,8 @@ function UIManager:schedule(time, action, ...) self._task_queue_dirty = true end dbg:guard(UIManager, 'schedule', - function(self, time, action) - assert(time.sec >= 0, "Only positive time allowed") + function(self, sched_time, action) + assert(sched_time >= 0, "Only positive time allowed") assert(action ~= nil, "No action") end) @@ -577,7 +577,7 @@ Schedules a task to be run a certain amount of seconds from now. function UIManager:scheduleIn(seconds, action, ...) -- We might run significantly late inside an UI frame, so we can't use the cached value here. -- It would also cause some bad interactions with the way nextTick & co behave. - local when = TimeVal:now() + TimeVal:fromnumber(seconds) + local when = time.now() + time.s(seconds) self:schedule(when, action, ...) end dbg:guard(UIManager, 'scheduleIn', @@ -1065,15 +1065,15 @@ function UIManager:discardEvents(set_or_seconds) -- sometimes > 500ms on some devices/temperatures. -- So, block for 400ms (to have it displayed) + 400ms -- for user reaction to it - delay = TimeVal:new{ sec = 0, usec = 800000 } + delay = time.ms(800) else -- On non-eInk screen, display is usually instantaneous - delay = TimeVal:new{ sec = 0, usec = 400000 } + delay = time.ms(400) end else -- we expect a number - delay = TimeVal:new{ sec = set_or_seconds, usec = 0 } + delay = time.s(set_or_seconds) end - self._discard_events_till = TimeVal:now() + delay + self._discard_events_till = time.now() + delay end --[[-- @@ -1089,7 +1089,7 @@ function UIManager:sendEvent(event) -- Ensure discardEvents if self._discard_events_till then - if TimeVal:now() < self._discard_events_till then + if time.now() < self._discard_events_till then return else self._discard_events_till = nil @@ -1173,10 +1173,10 @@ end --[[ function UIManager:getNextTaskTimes(count) - count = count or 1 + count = math.min(count or 1, #self._task_queue) local times = {} - for i = 1, math.min(count, #self._task_queue) do - times[i] = self._task_queue[i].time - TimeVal:now() + for i = 1, count do + times[i] = self._task_queue[i].time - time.now() end return times end @@ -1184,14 +1184,14 @@ end function UIManager:getNextTaskTime() if #self._task_queue > 0 then - return self._task_queue[1].time - TimeVal:now() + return self._task_queue[1].time - time:now() else return nil end end function UIManager:_checkTasks() - self._now = TimeVal:now() + self._now = time.now() local wait_until = nil -- task.action may schedule other events @@ -1202,8 +1202,8 @@ function UIManager:_checkTasks() break end local next_task = self._task_queue[1] - local task_tv = next_task.time or TimeVal.zero - if task_tv <= self._now then + local task_time = next_task.time or 0 + if task_time <= self._now then -- remove from table local task = table.remove(self._task_queue, 1) -- task is pending to be executed right now. do it. @@ -1213,7 +1213,7 @@ function UIManager:_checkTasks() else -- queue is sorted in ascendant order, safe to assume all items -- are future tasks for now - wait_until = next_task.time + wait_until = task_time break end end @@ -1222,9 +1222,9 @@ function UIManager:_checkTasks() end --[[-- -Returns a TimeVal object corresponding to the last UI tick. +Returns a time (fts) corresponding to the last tick. -This is essentially a cached TimeVal:now(), computed at the top of every iteration of the main UI loop, +This is essentially a cached time.now(), computed at the top of every iteration of the main UI loop, (right before checking/running scheduled tasks). This is mainly useful to compute/schedule stuff in the same time scale as the UI loop (i.e., MONOTONIC), without having to resort to a syscall. @@ -1233,7 +1233,7 @@ unless you're blocking the UI for a significant amount of time in a single UI fr That is to say, its granularity is an UI frame. -Prefer the appropriate TimeVal method for your needs if you require perfect accuracy or better granularity +Prefer the appropriate time function for your needs if you require perfect accuracy or better granularity (e.g., when you're actually working on the event loop *itself* (UIManager, Input, GestureDetector), or if you're dealing with intra-frame timers). @@ -1244,10 +1244,10 @@ function UIManager:getTime() end --[[-- -Returns a TimeVal object corresponding to the last UI tick plus the time in standby and suspend. +Returns a time (fts) corresponding to the last UI tick plus the time in standby. ]] function UIManager:getElapsedTimeSinceBoot() - return self:getTime() + Device.total_standby_tv + Device.total_suspend_tv + return self:getTime() + Device.total_standby_time + Device.total_suspend_time end -- precedence of refresh modes: @@ -1676,7 +1676,7 @@ function UIManager:handleInput() -- We pass that on as an absolute deadline, not a relative wait time. if wait_us then - deadline = now + TimeVal:new{ usec = wait_us } + deadline = now + time.us(wait_us) end -- If there's a scheduled task pending, that puts an upper bound on how long to wait. @@ -1721,7 +1721,6 @@ function UIManager:handleInput() end end - function UIManager:onRotation() self:setDirty("all", "full") self:forceRePaint() diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index 9fc83800c..de9288a03 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -18,7 +18,6 @@ local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget") local ScrollTextWidget = require("ui/widget/scrolltextwidget") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") -local TimeVal = require("ui/timeval") local TitleBar = require("ui/widget/titlebar") local Translator = require("ui/translator") local UIManager = require("ui/uimanager") @@ -32,6 +31,7 @@ local C_ = _.pgettext local Input = Device.input local Screen = Device.screen local T = require("ffi/util").template +local time = require("ui/time") --[[ Display quick lookup word definition @@ -154,7 +154,7 @@ function DictQuickLookup:init() args = function(text, hold_duration) -- do this lookup in the same domain (dict/wikipedia) local lookup_wikipedia = self.is_wiki - if hold_duration >= TimeVal:new{ sec = 3, usec = 0 } then + if hold_duration >= time.s(3) then -- but allow switching domain with a long hold lookup_wikipedia = not lookup_wikipedia end diff --git a/frontend/ui/widget/footnotewidget.lua b/frontend/ui/widget/footnotewidget.lua index b200c78d1..84633aa52 100644 --- a/frontend/ui/widget/footnotewidget.lua +++ b/frontend/ui/widget/footnotewidget.lua @@ -13,13 +13,13 @@ local InputContainer = require("ui/widget/container/inputcontainer") local LineWidget = require("ui/widget/linewidget") local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget") local Size = require("ui/size") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local _ = require("gettext") local Screen = Device.screen local T = require("ffi/util").template +local time = require("ui/time") -- If we wanted to use the default font set for the book, -- we'd need to add a few functions to crengine and cre.cpp @@ -195,7 +195,7 @@ function FootnoteWidget:init() -- callback function when HoldReleaseText is handled as args args = function(text, hold_duration) if self.dialog then - local lookup_target = hold_duration < TimeVal:new{ sec = 3, usec = 0 } and "LookupWord" or "LookupWikipedia" + local lookup_target = hold_duration < time.s(3) and "LookupWord" or "LookupWikipedia" self.dialog:handleEvent( Event:new(lookup_target, text) ) diff --git a/frontend/ui/widget/frontlightwidget.lua b/frontend/ui/widget/frontlightwidget.lua index 15fd2912b..4db284e50 100644 --- a/frontend/ui/widget/frontlightwidget.lua +++ b/frontend/ui/widget/frontlightwidget.lua @@ -15,12 +15,12 @@ local NaturalLight = require("ui/widget/naturallightwidget") local ProgressWidget = require("ui/widget/progresswidget") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") -local TimeVal = require("ui/timeval") local TitleBar = require("ui/widget/titlebar") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local time = require("ui/time") local _ = require("gettext") local Screen = Device.screen @@ -30,7 +30,7 @@ local FrontLightWidget = FocusManager:new{ -- This should stay active during natural light configuration is_always_active = true, rate = Screen.low_pan_rate and 3 or 30, -- Widget update rate. - last_time = TimeVal.zero, -- Tracks last update time to prevent update spamming. + last_time = 0, -- Tracks last update time to prevent update spamming. } function FrontLightWidget:init() @@ -569,9 +569,9 @@ function FrontLightWidget:onTapProgress(arg, ges_ev) -- But limit the widget update frequency on E Ink. if Screen.low_pan_rate then - local current_time = TimeVal:now() - local last_time = self.last_time or TimeVal.zero - if current_time - last_time > TimeVal:new{ usec = 1000000 / self.rate } then + local current_time = time.now() + local last_time = self.last_time or 0 + if current_time - last_time > time.s(1 / self.rate) then self.last_time = current_time else -- Schedule a final update after we stop panning. diff --git a/frontend/ui/widget/htmlboxwidget.lua b/frontend/ui/widget/htmlboxwidget.lua index ad15c99f2..8b8113537 100644 --- a/frontend/ui/widget/htmlboxwidget.lua +++ b/frontend/ui/widget/htmlboxwidget.lua @@ -9,9 +9,9 @@ local GestureRange = require("ui/gesturerange") local InputContainer = require("ui/widget/container/inputcontainer") local Mupdf = require("ffi/mupdf") local Screen = Device.screen -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local logger = require("logger") +local time = require("ui/time") local util = require("util") local HtmlBoxWidget = InputContainer:new{ @@ -21,7 +21,7 @@ local HtmlBoxWidget = InputContainer:new{ page_count = 0, page_number = 1, hold_start_pos = nil, - hold_start_tv = nil, + hold_start_time = nil, html_link_tapped_callback = nil, } @@ -166,7 +166,7 @@ function HtmlBoxWidget:onHoldStartText(_, ges) return false -- let event be processed by other widgets end - self.hold_start_tv = UIManager:getTime() + self.hold_start_time = UIManager:getTime() return true end @@ -230,7 +230,7 @@ function HtmlBoxWidget:onHoldReleaseText(callback, ges) return false end - local hold_duration = TimeVal.now() - self.hold_start_tv + local hold_duration = time.now() - self.hold_start_time local page = self.document:openPage(self.page_number) local lines = page:getPageText() diff --git a/frontend/ui/widget/notification.lua b/frontend/ui/widget/notification.lua index 116196a8a..0bc45da02 100644 --- a/frontend/ui/widget/notification.lua +++ b/frontend/ui/widget/notification.lua @@ -13,10 +13,10 @@ local InputContainer = require("ui/widget/container/inputcontainer") local RectSpan = require("ui/widget/rectspan") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local Input = Device.input +local time = require("ui/time") local Screen = Device.screen local band = bit.band @@ -168,9 +168,9 @@ function Notification:_cleanShownStack(num) -- to follow what is happening). -- As a sanity check, we also forget those shown for -- more than 30s in case no close event was received. - local expire_tv = UIManager:getTime() - TimeVal:new{ sec = 30, usec = 0 } + local expire_time = UIManager:getTime() - time.s(30) for i=#Notification._nums_shown, 1, -1 do - if Notification._nums_shown[i] and Notification._nums_shown[i] > expire_tv then + if Notification._nums_shown[i] and Notification._nums_shown[i] > expire_time then break -- still shown (or not yet expired) end table.remove(Notification._nums_shown, i) diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 65bbc684f..a1322605f 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -24,11 +24,11 @@ local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local Math = require("optmath") local logger = require("logger") local dbg = require("dbg") +local time = require("ui/time") local util = require("util") local Screen = require("device").screen @@ -1852,18 +1852,18 @@ function TextBoxWidget:onHoldStartText(_, ges) -- check coordinates are actually inside our area if self.hold_start_x < 0 or self.hold_start_x > self.dimen.w or self.hold_start_y < 0 or self.hold_start_y > self.dimen.h then - self.hold_start_tv = nil -- don't process coming HoldRelease event + self.hold_start_time = nil -- don't process coming HoldRelease event return false -- let event be processed by other widgets end - self.hold_start_tv = UIManager:getTime() + self.hold_start_time = UIManager:getTime() return true end function TextBoxWidget:onHoldPanText(_, ges) -- We don't highlight the currently selected text, but just let this -- event pop up if we are not currently selecting text - if not self.hold_start_tv then + if not self.hold_start_time then return false end -- Don't let that event be processed by other widget @@ -1877,7 +1877,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) local hold_end_y = ges.pos.y - self.dimen.y -- check we have seen a HoldStart event - if not self.hold_start_tv then + if not self.hold_start_time then return false end -- check start and end coordinates are actually inside our area @@ -1888,7 +1888,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) return false end - local hold_duration = TimeVal.now() - self.hold_start_tv + local hold_duration = time.now() - self.hold_start_time -- If page contains an image, check if Hold is on this image and deal -- with it directly @@ -1950,7 +1950,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) -- a missed start event self.hold_start_x = nil self.hold_start_y = nil - self.hold_start_tv = nil + self.hold_start_time = nil if self.use_xtext then -- With xtext and fribidi, words may not be laid out in logical order, @@ -1982,7 +1982,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) -- to consider when looking for word boundaries) local selected_text = self._xtext:getSelectedWords(sel_start_idx, sel_end_idx, 50) - logger.dbg("onHoldReleaseText (duration:", hold_duration:tonumber(), ") :", + logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) callback(selected_text, hold_duration) return true @@ -2000,7 +2000,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges) end local selected_text = table.concat(self.charlist, "", sel_start_idx, sel_end_idx) - logger.dbg("onHoldReleaseText (duration:", hold_duration:tonumber(), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) + logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) callback(selected_text, hold_duration) return true end diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index 0bfb7d25b..3d694b341 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -14,12 +14,12 @@ local InputContainer = require("ui/widget/container/inputcontainer") local KeyboardLayoutDialog = require("ui/widget/keyboardlayoutdialog") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") +local time = require("ui/time") local util = require("util") local Screen = Device.screen @@ -665,8 +665,7 @@ function VirtualKeyPopup:init() } }, } - self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) - self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override } + self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0)) if Device:hasKeys() then self.key_events.Close = { {Device.input.group.Back}, doc = "close keyboard" } @@ -787,8 +786,7 @@ function VirtualKeyboard:init() self.min_layer = keyboard.min_layer self.max_layer = keyboard.max_layer self:initLayer(self.keyboard_layer) - self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) - self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override } + self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0)) if Device:hasKeys() then self.key_events.Close = { {"Back"}, doc = "close keyboard" } end diff --git a/plugins/autosuspend.koplugin/main.lua b/plugins/autosuspend.koplugin/main.lua index 55cbccfe8..ab6713182 100644 --- a/plugins/autosuspend.koplugin/main.lua +++ b/plugins/autosuspend.koplugin/main.lua @@ -13,11 +13,11 @@ end local Event = require("ui/event") local NetworkMgr = require("ui/network/manager") local PluginShare = require("pluginshare") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") local util = require("util") +local time = require("ui/time") local _ = require("gettext") local Math = require("optmath") local T = require("ffi/util").template @@ -32,7 +32,7 @@ local AutoSuspend = WidgetContainer:new{ autoshutdown_timeout_seconds = default_autoshutdown_timeout_seconds, auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds, auto_standby_timeout_seconds = default_auto_standby_timeout_seconds, - last_action_tv = TimeVal.zero, + last_action_time = 0, is_standby_scheduled = false, task = nil, standby_task = nil, @@ -58,7 +58,7 @@ function AutoSuspend:_schedule(shutdown_only) return end - local suspend_delay, shutdown_delay + local suspend_delay_seconds, shutdown_delay_seconds local is_charging -- On devices with an auxiliary battery, we only care about the auxiliary battery being charged... local powerd = Device:getPowerDevice() @@ -68,29 +68,29 @@ function AutoSuspend:_schedule(shutdown_only) is_charging = powerd:isCharging() end if PluginShare.pause_auto_suspend or is_charging then - suspend_delay = self.auto_suspend_timeout_seconds - shutdown_delay = self.autoshutdown_timeout_seconds + suspend_delay_seconds = self.auto_suspend_timeout_seconds + shutdown_delay_seconds = self.autoshutdown_timeout_seconds else - local now_tv = UIManager:getElapsedTimeSinceBoot() - suspend_delay = self.auto_suspend_timeout_seconds - (now_tv - self.last_action_tv):tonumber() - shutdown_delay = self.autoshutdown_timeout_seconds - (now_tv - self.last_action_tv):tonumber() + local now = UIManager:getElapsedTimeSinceBoot() + suspend_delay_seconds = self.auto_suspend_timeout_seconds - time.to_number(now - self.last_action_time) + shutdown_delay_seconds = self.autoshutdown_timeout_seconds - time.to_number(now - self.last_action_time) end -- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that. - if self:_enabledShutdown() and shutdown_delay <= 0 then + if self:_enabledShutdown() and shutdown_delay_seconds <= 0 then logger.dbg("AutoSuspend: initiating shutdown") UIManager:poweroff_action() - elseif self:_enabled() and suspend_delay <= 0 and not shutdown_only then + elseif self:_enabled() and suspend_delay_seconds <= 0 and not shutdown_only then logger.dbg("AutoSuspend: will suspend the device") UIManager:suspend() else if self:_enabled() and not shutdown_only then - logger.dbg("AutoSuspend: scheduling next suspend check in", suspend_delay) - UIManager:scheduleIn(suspend_delay, self.task) + logger.dbg("AutoSuspend: scheduling next suspend check in", suspend_delay_seconds) + UIManager:scheduleIn(suspend_delay_seconds, self.task) end if self:_enabledShutdown() then - logger.dbg("AutoSuspend: scheduling next shutdown check in", shutdown_delay) - UIManager:scheduleIn(shutdown_delay, self.task) + logger.dbg("AutoSuspend: scheduling next shutdown check in", shutdown_delay_seconds) + UIManager:scheduleIn(shutdown_delay_seconds, self.task) end end end @@ -104,14 +104,14 @@ end function AutoSuspend:_start() if self:_enabled() or self:_enabledShutdown() then - logger.dbg("AutoSuspend: start suspend/shutdown timer at", self.last_action_tv:tonumber()) + logger.dbg("AutoSuspend: start suspend/shutdown timer at", time.format_time(self.last_action_time)) self:_schedule() end end function AutoSuspend:_start_standby() if self:_enabledStandby() then - logger.dbg("AutoSuspend: start standby timer at", self.last_action_tv:tonumber()) + logger.dbg("AutoSuspend: start standby timer at", time.format_time(self.last_action_time)) self:_schedule_standby() end end @@ -119,7 +119,7 @@ end -- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit function AutoSuspend:_restart() if self:_enabledShutdown() then - logger.dbg("AutoSuspend: restart shutdown timer at", self.last_action_tv:tonumber()) + logger.dbg("AutoSuspend: restart shutdown timer at", time.format_time(self.last_action_time)) self:_schedule(true) end end @@ -161,7 +161,7 @@ function AutoSuspend:init() -- Make sure we only have an AllowStandby handler when we actually want one... self:toggleStandbyHandler(self:_enabledStandby()) - self.last_action_tv = UIManager:getElapsedTimeSinceBoot() + self.last_action_time = UIManager:getElapsedTimeSinceBoot() self:_start() self:_start_standby() @@ -185,7 +185,7 @@ end function AutoSuspend:onInputEvent() logger.dbg("AutoSuspend: onInputEvent") - self.last_action_tv = UIManager:getElapsedTimeSinceBoot() + self.last_action_time = UIManager:getElapsedTimeSinceBoot() end function AutoSuspend:_unschedule_standby() @@ -219,41 +219,41 @@ function AutoSuspend:_schedule_standby() end -- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay. - local standby_delay + local standby_delay_seconds if NetworkMgr:isWifiOn() then -- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks). --logger.dbg("AutoSuspend: WiFi is on, delaying standby") - standby_delay = self.auto_standby_timeout_seconds + standby_delay_seconds = self.auto_standby_timeout_seconds elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then -- Don't enter standby when charging on devices where charging prevents entering low power states. -- NOTE: Minor simplification here, we currently don't do the hasAuxBattery dance like in _schedule, -- because all the hasAuxBattery devices can currently enter PM states while charging ;). --logger.dbg("AutoSuspend: charging, delaying standby") - standby_delay = self.auto_standby_timeout_seconds + standby_delay_seconds = self.auto_standby_timeout_seconds else - local now_tv = UIManager:getElapsedTimeSinceBoot() - standby_delay = self.auto_standby_timeout_seconds - (now_tv - self.last_action_tv):tonumber() + local now = UIManager:getElapsedTimeSinceBoot() + standby_delay_seconds = self.auto_standby_timeout_seconds - time.to_number(now - self.last_action_time) -- If we blow past the deadline on the first call of a scheduling cycle, -- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet... - if not self.is_standby_scheduled and standby_delay <= 0 then + if not self.is_standby_scheduled and standby_delay_seconds <= 0 then -- If this happens, it means we hit LeaveStandby or Resume *before* consuming new input events, -- e.g., if there weren't any input events at all (woken up by an alarm), -- or if the only input events we consumed did not trigger an InputEvent event (woken up by gyro events), - -- meaning self.last_action_tv is further in the past than it ought to. + -- meaning self.last_action_time is further in the past than it ought to. -- Delay by the full amount to avoid further bad scheduling interactions. - standby_delay = self.auto_standby_timeout_seconds + standby_delay_seconds = self.auto_standby_timeout_seconds end end - if standby_delay <= 0 then + if standby_delay_seconds <= 0 then -- We blew the deadline, tell UIManager we're ready to enter standby self:allowStandby() else -- Reschedule standby for the full or remaining delay -- NOTE: This is fairly chatty, given the low delays, but really helpful nonetheless... :/ - logger.dbg("AutoSuspend: scheduling next standby check in", standby_delay) - UIManager:scheduleIn(standby_delay, self.standby_task) + logger.dbg("AutoSuspend: scheduling next standby check in", standby_delay_seconds) + UIManager:scheduleIn(standby_delay_seconds, self.standby_task) -- Prevent standby until we actually blow the deadline if not self.is_standby_scheduled then @@ -318,7 +318,7 @@ function AutoSuspend:onResume() end -- Unschedule in case we tripped onUnexpectedWakeupLimit first... self:_unschedule() - -- We should always follow an InputEvent, so last_action_tv is already up to date :). + -- We should always follow an InputEvent, so last_action_time is already up to date :). self:_start() self:_unschedule_standby() self:_start_standby() @@ -391,13 +391,13 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting, ok_text = _("Set timeout"), title_text = title, info_text = info, - callback = function(time) + callback = function(spinner) if time_scale == 2 then - self[setting] = (time.hour * 24 + time.min) * 3600 + self[setting] = (spinner.hour * 24 + spinner.min) * 3600 elseif time_scale == 1 then - self[setting] = time.hour * 3600 + time.min * 60 + self[setting] = spinner.hour * 3600 + spinner.min * 60 else - self[setting] = time.hour * 60 + time.min + self[setting] = spinner.hour * 60 + spinner.min end self[setting] = Math.clamp(self[setting], range[1], range[2]) G_reader_settings:saveSetting(setting, self[setting]) @@ -571,7 +571,7 @@ function AutoSuspend:AllowStandbyHandler() if next_task_time then -- Wake up slightly after the formerly scheduled event, -- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice). - wake_in = math.floor(next_task_time:tonumber()) + 1 + wake_in = math.floor(time.to_number(next_task_time)) + 1 else wake_in = math.huge end @@ -583,12 +583,12 @@ function AutoSuspend:AllowStandbyHandler() -- This obviously needs a matching implementation in Device, the canonical one being Kobo. Device:standby(wake_in) - logger.dbg("AutoSuspend: left standby after", Device.last_standby_tv:tonumber(), "s") + logger.dbg("AutoSuspend: left standby after", time.format_time(Device.last_standby_time), "s") -- We delay the LeaveStandby event (our onLeaveStandby handler is responsible for rescheduling everything properly), -- to make sure UIManager will consume the input events that woke us up first -- (in case we were woken up by user input, as opposed to an rtc wake alarm)! - -- (This ensures we'll use an up to date last_action_tv, and that it only ever gets updated from *user* input). + -- (This ensures we'll use an up to date last_action_time, and that it only ever gets updated from *user* input). -- NOTE: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick. UIManager:tickAfterNext(self.leave_standby_task) end diff --git a/plugins/autoturn.koplugin/main.lua b/plugins/autoturn.koplugin/main.lua index ba3bfeced..bbd39bd1f 100644 --- a/plugins/autoturn.koplugin/main.lua +++ b/plugins/autoturn.koplugin/main.lua @@ -1,10 +1,10 @@ local Device = require("device") local Event = require("ui/event") local PluginShare = require("pluginshare") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") +local time = require("ui/time") local _ = require("gettext") local T = require("ffi/util").template @@ -14,7 +14,7 @@ local AutoTurn = WidgetContainer:new{ autoturn_sec = 0, autoturn_distance = 1, enabled = false, - last_action_tv = TimeVal.zero, + last_action_time = 0, task = nil, } @@ -28,21 +28,21 @@ function AutoTurn:_schedule() return end - local delay = self.last_action_tv + TimeVal:new{ sec = self.autoturn_sec, usec = 0 } - UIManager:getTime() - delay = delay:tonumber() + local delay = self.last_action_time + time.s(self.autoturn_sec) - UIManager:getTime() if delay <= 0 then if UIManager:getTopWidget() == "ReaderUI" then logger.dbg("AutoTurn: go to next page") self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance)) - self.last_action_tv = UIManager:getTime() + self.last_action_time = UIManager:getTime() end logger.dbg("AutoTurn: schedule in", self.autoturn_sec) UIManager:scheduleIn(self.autoturn_sec, self.task) self.scheduled = true else - logger.dbg("AutoTurn: schedule in", delay) - UIManager:scheduleIn(delay, self.task) + local delay_s = time.to_number(delay) + logger.dbg("AutoTurn: schedule in", delay_s, "s") + UIManager:scheduleIn(delay_s, self.task) self.scheduled = true end end @@ -58,10 +58,10 @@ end function AutoTurn:_start() if self:_enabled() then - local now_tv = UIManager:getTime() - logger.dbg("AutoTurn: start at", now_tv:tonumber()) + local now = UIManager:getTime() + logger.dbg("AutoTurn: start at", time.format_time(now)) PluginShare.pause_auto_suspend = true - self.last_action_tv = now_tv + self.last_action_time = now self:_schedule() local text @@ -107,7 +107,7 @@ end function AutoTurn:onInputEvent() logger.dbg("AutoTurn: onInputEvent") - self.last_action_tv = UIManager:getTime() + self.last_action_time = UIManager:getTime() end function AutoTurn:onEnterStandby() @@ -122,9 +122,9 @@ function AutoTurn:onSuspend() end function AutoTurn:_onLeaveStandby() - self.last_action_tv = self.last_action_tv - Device.last_standby_tv + self.last_action_time = self.last_action_time - Device.last_standby_time - -- We messed with last_action_tv, so a complete reschedule has to be done. + -- We messed with last_action_time, so a complete reschedule has to be done. if self:_enabled() then self:_unschedule() self:_schedule() diff --git a/plugins/backgroundrunner.koplugin/commandrunner.lua b/plugins/backgroundrunner.koplugin/commandrunner.lua index f90b9fd8a..3580e5b2f 100644 --- a/plugins/backgroundrunner.koplugin/commandrunner.lua +++ b/plugins/backgroundrunner.koplugin/commandrunner.lua @@ -1,6 +1,6 @@ local logger = require("logger") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") +local time = require("ui/time") local CommandRunner = { pio = nil, @@ -37,7 +37,7 @@ function CommandRunner:start(job) assert(self.pio == nil) assert(self.job == nil) self.job = job - self.job.start_tv = UIManager:getTime() + self.job.start_time = UIManager:getTime() assert(type(self.job.executable) == "string") local command = self:createEnvironment() .. " " .. "sh plugins/backgroundrunner.koplugin/luawrapper.sh " .. @@ -77,7 +77,7 @@ function CommandRunner:poll() UIManager:allowStandby() self.pio:close() self.pio = nil - self.job.end_tv = TimeVal:now() + self.job.end_time = time.now() local job = self.job self.job = nil return job diff --git a/plugins/backgroundrunner.koplugin/main.lua b/plugins/backgroundrunner.koplugin/main.lua index 451e5e22b..d7d747b76 100644 --- a/plugins/backgroundrunner.koplugin/main.lua +++ b/plugins/backgroundrunner.koplugin/main.lua @@ -9,10 +9,10 @@ end local CommandRunner = require("commandrunner") local PluginShare = require("pluginshare") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") +local time = require("ui/time") local _ = require("gettext") -- BackgroundRunner is an experimental feature to execute non-critical jobs in @@ -72,9 +72,9 @@ local _ = require("gettext") -- bad_command: boolean, whether the command is not found. Not available for -- function executable. -- blocked: boolean, whether the job is blocked. --- start_tv: number, the TimeVal when the job was started. --- end_tv: number, the TimeVal when the job was stopped. --- insert_tv: number, the TimeVal when the job was inserted into queue. +-- start_time: number, the time (fts) when the job was started. +-- end_time: number, the time (fts) when the job was stopped. +-- insert_time: number, the time (fts) when the job was inserted into queue. -- (All of them in the monotonic time scale, like the main event loop & task queue). local BackgroundRunner = { @@ -117,9 +117,9 @@ end function BackgroundRunner:_finishJob(job) assert(self ~= nil) if type(job.executable) == "function" then - local tv_diff = job.end_tv - job.start_tv - local threshold = TimeVal:new{ sec = 1, usec = 0 } - job.timeout = (tv_diff > threshold) + local time_diff = job.end_time - job.start_time + local threshold = time.s(1) + job.timeout = (time_diff > threshold) end job.blocked = job.timeout if not job.blocked and self:_shouldRepeat(job) then @@ -141,7 +141,7 @@ function BackgroundRunner:_executeJob(job) CommandRunner:start(job) return true elseif type(job.executable) == "function" then - job.start_tv = UIManager:getTime() + job.start_time = UIManager:getTime() local status, err = pcall(job.executable) if status then job.result = 0 @@ -149,7 +149,7 @@ function BackgroundRunner:_executeJob(job) job.result = 1 job.exception = err end - job.end_tv = TimeVal:now() + job.end_time = time.now() self:_finishJob(job) return true else @@ -176,10 +176,10 @@ function BackgroundRunner:_execute() local round = 0 while #self.jobs > 0 do local job = table.remove(self.jobs, 1) - if job.insert_tv == nil then + if job.insert_time == nil then -- Jobs are first inserted to jobs table from external users. -- So they may not have an insert field. - job.insert_tv = UIManager:getTime() + job.insert_time = UIManager:getTime() end local should_execute = false local should_ignore = false @@ -192,7 +192,7 @@ function BackgroundRunner:_execute() end elseif type(job.when) == "number" then if job.when >= 0 then - should_execute = ((UIManager:getTime() - job.insert_tv) >= TimeVal:fromnumber(job.when)) + should_execute = (UIManager:getTime() - job.insert_time >= time.s(job.when)) else should_ignore = true end @@ -253,7 +253,7 @@ end function BackgroundRunner:_insert(job) assert(self ~= nil) - job.insert_tv = UIManager:getTime() + job.insert_time = UIManager:getTime() table.insert(self.jobs, job) end diff --git a/plugins/calibre.koplugin/metadata.lua b/plugins/calibre.koplugin/metadata.lua index eba130ec0..7dc95cbf6 100644 --- a/plugins/calibre.koplugin/metadata.lua +++ b/plugins/calibre.koplugin/metadata.lua @@ -8,12 +8,12 @@ of storing it. @module koplugin.calibre.metadata --]]-- -local TimeVal = require("ui/timeval") local lfs = require("libs/libkoreader-lfs") local rapidjson = require("rapidjson") local logger = require("logger") local parser = require("parser") local util = require("util") +local time = require("ui/time") local used_metadata = { "uuid", @@ -242,7 +242,7 @@ end function CalibreMetadata:init(dir, is_search) if not dir then return end - local start = TimeVal:now() + local start_time = time.now() self.path = dir local ok_meta, ok_drive, file_meta, file_drive = findCalibreFiles(dir) self.driveinfo = file_drive @@ -261,12 +261,12 @@ function CalibreMetadata:init(dir, is_search) if is_search then self:cleanUnused(is_search) msg = string.format("(search) in %.3f milliseconds: %d books", - TimeVal:getDurationMs(start), #self.books) + time.to_ms(time.since(start_time())), #self.books) else local deleted_count = self:prune() self:cleanUnused() msg = string.format("in %.3f milliseconds: %d books. %d pruned", - TimeVal:getDurationMs(start), #self.books, deleted_count) + time.to_ms(time.since(start_time())), #self.books, deleted_count) end logger.info(string.format("calibre info loaded from disk %s", msg)) return true diff --git a/plugins/calibre.koplugin/search.lua b/plugins/calibre.koplugin/search.lua index f68804197..04d8a01a9 100644 --- a/plugins/calibre.koplugin/search.lua +++ b/plugins/calibre.koplugin/search.lua @@ -13,12 +13,12 @@ local InputContainer = require("ui/widget/container/inputcontainer") local Menu = require("ui/widget/menu") local Persist = require("persist") local Screen = require("device").screen -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") local rapidjson = require("rapidjson") local util = require("util") +local time = require("ui/time") local _ = require("gettext") local T = require("ffi/util").template @@ -325,10 +325,10 @@ function CalibreSearch:find(option) end -- measure time elapsed searching - local start = TimeVal:now() + local start_time = time.now() self:browse(option) logger.info(string.format("search done in %.3f milliseconds (%s, %s, %s, %s, %s)", - TimeVal:getDurationMs(start), + time.to_ms(time.since(start_time)), option == "find" and "books" or option, "case sensitive: " .. tostring(not self.case_insensitive), "title: " .. tostring(self.find_by_title), @@ -552,7 +552,7 @@ end -- get metadata from cache or calibre files function CalibreSearch:getMetadata() - local start = TimeVal:now() + local start_time = time.now() local template = "metadata: %d books imported from %s in %.3f milliseconds" -- try to load metadata from cache @@ -594,7 +594,7 @@ function CalibreSearch:getMetadata() end end if is_newer then - logger.info(string.format(template, #cache, "cache", TimeVal:getDurationMs(start))) + logger.info(string.format(template, #cache, "cache", time.to_ms(time.since(start_time)))) return cache else logger.warn("cache is older than metadata, ignoring it") @@ -623,7 +623,7 @@ function CalibreSearch:getMetadata() logger.info("Failed to serialize calibre metadata cache:", err) end end - logger.info(string.format(template, #books, "calibre", TimeVal:getDurationMs(start))) + logger.info(string.format(template, #books, "calibre", time.to_ms(time.since(start_time)))) return books end diff --git a/plugins/coverbrowser.koplugin/bookinfomanager.lua b/plugins/coverbrowser.koplugin/bookinfomanager.lua index bb7c3e7ee..6d50c9c4d 100644 --- a/plugins/coverbrowser.koplugin/bookinfomanager.lua +++ b/plugins/coverbrowser.koplugin/bookinfomanager.lua @@ -7,12 +7,12 @@ local FFIUtil = require("ffi/util") local InfoMessage = require("ui/widget/infomessage") local RenderImage = require("ui/renderimage") local SQ3 = require("lua-ljsqlite3/init") -local TimeVal = require("ui/timeval") local UIManager = require("ui/uimanager") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") local util = require("util") local zstd = require("ffi/zstd") +local time = require("ui/time") local _ = require("gettext") local N_ = _.ngettext local T = FFIUtil.template @@ -137,8 +137,8 @@ function BookInfoManager:init() self.subprocesses_collector = nil self.subprocesses_collect_interval = 10 -- do that every 10 seconds self.subprocesses_pids = {} - self.subprocesses_last_added_tv = TimeVal.zero - self.subprocesses_killall_timeout_tv = TimeVal:new{ sec = 300, usec = 0 } -- cleanup timeout for stuck subprocesses + self.subprocesses_last_added_time = 0 + self.subprocesses_killall_timeout_time = time.s(300) -- cleanup timeout for stuck subprocesses -- 300 seconds should be more than enough to open and get info from 9-10 books -- Whether to use former blitbuffer:scale() (default to using MuPDF) self.use_legacy_image_scaling = G_reader_settings:isTrue("legacy_image_scaling") @@ -657,7 +657,7 @@ function BookInfoManager:collectSubprocesses() -- the user has not left FileManager or changed page - that would -- have caused a terminateBackgroundJobs() - if we're here, it's -- that user has left reader in FileBrower and went away) - if TimeVal:now() > self.subprocesses_last_added_tv + self.subprocesses_killall_timeout_tv then + if time.now() > self.subprocesses_last_added_time + self.subprocesses_killall_timeout_time then logger.warn("Some subprocesses were running for too long, killing them") self:terminateBackgroundJobs() -- we'll collect them next time we're run @@ -730,7 +730,7 @@ function BookInfoManager:extractInBackground(files) -- counter on each task, and undo that inside collectSubprocesses() zombie reaper. UIManager:preventStandby() table.insert(self.subprocesses_pids, task_pid) - self.subprocesses_last_added_tv = TimeVal:now() + self.subprocesses_last_added_time = time.now() -- We need to collect terminated jobs pids (so they do not stay "zombies" -- and fill linux processes table) diff --git a/plugins/gestures.koplugin/main.lua b/plugins/gestures.koplugin/main.lua index 8bc9e49ce..f186d2562 100644 --- a/plugins/gestures.koplugin/main.lua +++ b/plugins/gestures.koplugin/main.lua @@ -17,6 +17,7 @@ local SpinWidget = require("ui/widget/spinwidget") local UIManager = require("ui/uimanager") local util = require("util") local T = FFIUtil.template +local time = require("ui/time") local _ = require("gettext") local logger = require("logger") @@ -478,16 +479,16 @@ Any other taps made within this interval after a first tap will be considered ac The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = GestureDetector:getInterval("ges_tap_interval")/1000, + value = time.to_ms(GestureDetector.ges_tap_interval), value_min = 0, value_max = 2000, value_step = 50, value_hold_step = 200, ok_text = _("Set interval"), - default_value = GestureDetector.TAP_INTERVAL/1000, + default_value = GestureDetector.TAP_INTERVAL_MS, callback = function(spin) - G_reader_settings:saveSetting("ges_tap_interval", spin.value*1000) - GestureDetector:setNewInterval("ges_tap_interval", spin.value*1000) + G_reader_settings:saveSetting("ges_tap_interval_ms", spin.value) + GestureDetector.ges_tap_interval = time.ms(spin.value) end } UIManager:show(items) @@ -504,7 +505,7 @@ Any other taps made within this interval after a first tap will be considered ac The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0)/1000, + value = time.to_ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0)), value_min = 0, value_max = 2000, value_step = 50, @@ -512,7 +513,7 @@ The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 ( ok_text = _("Set interval"), default_value = 0, callback = function(spin) - G_reader_settings:saveSetting("ges_tap_interval_on_keyboard", spin.value*1000) + G_reader_settings:saveSetting("ges_tap_interval_on_keyboard_ms", spin.value) end } UIManager:show(items) @@ -529,16 +530,16 @@ When double tap is enabled, this sets the time to wait for the second tap. A sin The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = GestureDetector:getInterval("ges_double_tap_interval")/1000, + value = time.to_ms(GestureDetector.ges_double_tap_interval), value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - default_value = GestureDetector.DOUBLE_TAP_INTERVAL/1000, + default_value = GestureDetector.DOUBLE_TAP_INTERVAL_MS, callback = function(spin) - G_reader_settings:saveSetting("ges_double_tap_interval", spin.value*1000) - GestureDetector:setNewInterval("ges_double_tap_interval", spin.value*1000) + G_reader_settings:saveSetting("ges_double_tap_interval_ms", spin.value) + GestureDetector.ges_double_tap_interval = time.ms(spin.value) end } UIManager:show(items) @@ -555,16 +556,16 @@ This sets the allowed duration of any of the two fingers touch/release for the c The duration value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = GestureDetector:getInterval("ges_two_finger_tap_duration")/1000, + value = time.to_ms(GestureDetector.ges_two_finger_tap_duration), value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set duration"), - default_value = GestureDetector.TWO_FINGER_TAP_DURATION/1000, + default_value = GestureDetector.TWO_FINGER_TAP_DURATION_MS, callback = function(spin) - G_reader_settings:saveSetting("ges_two_finger_tap_duration", spin.value*1000) - GestureDetector:setNewInterval("ges_two_finger_tap_duration", spin.value*1000) + G_reader_settings:saveSetting("ges_two_finger_tap_duration_ms", spin.value) + GestureDetector.ges_two_finger_tap_duration = time.ms(spin.value) end } UIManager:show(items) @@ -581,16 +582,16 @@ If a touch is not released in this interval, it is considered a long-press. On d The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = GestureDetector:getInterval("ges_hold_interval")/1000, + value = time.to_ms(GestureDetector.ges_hold_interval), value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - default_value = GestureDetector.HOLD_INTERVAL/1000, + default_value = GestureDetector.HOLD_INTERVAL_MS, callback = function(spin) - G_reader_settings:saveSetting("ges_hold_interval", spin.value*1000) - GestureDetector:setNewInterval("ges_hold_interval", spin.value*1000) + G_reader_settings:saveSetting("ges_hold_interval_ms", spin.value) + GestureDetector.ges_hold_interval = time.ms(spin.value) end } UIManager:show(items) @@ -607,16 +608,16 @@ This sets the maximum delay between the start and the end of a swipe for it to b The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), width = math.floor(Screen:getWidth() * 0.75), - value = GestureDetector:getInterval("ges_swipe_interval")/1000, + value = time.to_ms(GestureDetector.ges_swipe_interval), value_min = 100, value_max = 2000, value_step = 100, value_hold_step = 500, ok_text = _("Set interval"), - default_value = GestureDetector.SWIPE_INTERVAL/1000, + default_value = GestureDetector.SWIPE_INTERVAL_MS, callback = function(spin) - G_reader_settings:saveSetting("ges_swipe_interval", spin.value*1000) - GestureDetector:setNewInterval("ges_swipe_interval", spin.value*1000) + G_reader_settings:saveSetting("ges_swipe_interval_ms", spin.value) + GestureDetector.ges_swipe_interval = time.ms(spin.value) end } UIManager:show(items) diff --git a/plugins/systemstat.koplugin/main.lua b/plugins/systemstat.koplugin/main.lua index 8a268392b..40dca1a33 100644 --- a/plugins/systemstat.koplugin/main.lua +++ b/plugins/systemstat.koplugin/main.lua @@ -3,6 +3,7 @@ local Dispatcher = require("dispatcher") local KeyValuePage = require("ui/widget/keyvaluepage") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local time = require("ui/time") local util = require("util") local _ = require("gettext") @@ -48,11 +49,11 @@ function SystemStat:appendCounters() util.secondsToClockDuration("", os.difftime(os.time(), self.start_sec), false, true, true)}) if Device:canSuspend() then self:put({" " .. _("Time in suspend"), - util.secondsToClockDuration("", Device.total_suspend_tv:tonumber(), false, true, true)}) + util.secondsToClockDuration("", time.to_number(Device.total_suspend_time), false, true, true)}) end if Device:canStandby() then self:put({" " .. _("Time in standby"), - util.secondsToClockDuration("", Device.total_standby_tv:tonumber(), false, true, true)}) + util.secondsToClockDuration("", time.to_number(Device.total_standby_time), false, true, true)}) end self:put({_("Counters"), ""}) self:put({_(" wake-ups"), self.wakeup_count}) diff --git a/spec/unit/background_runner_spec.lua b/spec/unit/background_runner_spec.lua index a9c08f4f5..550fd1886 100644 --- a/spec/unit/background_runner_spec.lua +++ b/spec/unit/background_runner_spec.lua @@ -39,6 +39,7 @@ describe("BackgroundRunner widget tests", function() MockTime:increase(2) UIManager:handleInput() + assert.is_false(executed) MockTime:increase(9) UIManager:handleInput() assert.is_false(executed) @@ -132,7 +133,7 @@ describe("BackgroundRunner widget tests", function() table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end @@ -157,7 +158,7 @@ describe("BackgroundRunner widget tests", function() table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end @@ -171,11 +172,11 @@ describe("BackgroundRunner widget tests", function() ENV1 = "yes", ENV2 = "no", } - job.end_tv = nil + job.end_time = nil table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end @@ -206,7 +207,7 @@ describe("BackgroundRunner widget tests", function() table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end @@ -216,12 +217,12 @@ describe("BackgroundRunner widget tests", function() assert.is_false(job.timeout) assert.is_false(job.bad_command) - job.end_tv = nil + job.end_time = nil env2 = "no" table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end @@ -244,7 +245,7 @@ describe("BackgroundRunner widget tests", function() table.insert(PluginShare.backgroundJobs, job) notifyBackgroundJobsUpdated() - while job.end_tv == nil do + while job.end_time == nil do MockTime:increase(2) UIManager:handleInput() end diff --git a/spec/unit/mock_time.lua b/spec/unit/mock_time.lua index 2c335f7ea..a64bab8e9 100644 --- a/spec/unit/mock_time.lua +++ b/spec/unit/mock_time.lua @@ -1,5 +1,6 @@ require("commonrequire") local TimeVal = require("ui/timeval") +local time = require("ui/time") local ffi = require("ffi") local dummy = require("ffi/posix_h") local logger = require("logger") @@ -21,6 +22,10 @@ local MockTime = { realtime = 0, boottime = 0, boottime_or_realtime_coarse = 0, + monotonic_time = 0, + realtime_time = 0, + boottime_time = 0, + boottime_or_realtime_coarse_time = 0, } function MockTime:install() @@ -100,7 +105,72 @@ function MockTime:install() logger.dbg("MockTime:TimeVal.now: ", self.monotonic) return TimeVal:new{ sec = self.monotonic } end -end + + if self.original_tv_realtime_time == nil then + self.original_tv_realtime_time = time.realtime + assert(self.original_tv_realtime_time ~= nil) + end + if self.original_tv_realtime_coarse_time == nil then + self.original_tv_realtime_coarse_time = time.realtime_coarse + assert(self.original_tv_realtime_coarse_time ~= nil) + end + if self.original_tv_monotonic_time == nil then + self.original_tv_monotonic_time = time.monotonic + assert(self.original_tv_monotonic_time ~= nil) + end + if self.original_tv_monotonic_coarse_time == nil then + self.original_tv_monotonic_coarse_time = time.monotonic_coarse + assert(self.original_tv_monotonic_coarse_time ~= nil) + end + if self.original_tv_boottime_time == nil then + self.original_tv_boottime_time = time.boottime + assert(self.original_tv_boottime_time ~= nil) + end + if self.original_tv_boottime_or_realtime_coarse_time == nil then + self.original_tv_boottime_or_realtime_coarse_time = time.boottime_or_realtime_coarse + assert(self.original_tv_boottime_or_realtime_coarse_time ~= nil) + end + if self.original_tv_now == nil then + self.original_tv_now = time.now + assert(self.original_tv_now ~= nil) + end + + -- Store both REALTIME & MONOTONIC clocks for fts + self.realtime_time = os.time() * 1e6 + local timespec_time = ffi.new("struct timespec") + C.clock_gettime(C.CLOCK_MONOTONIC_COARSE, timespec_time) + self.monotonic = tonumber(timespec.tv_sec) * 1e6 + + time.realtime = function() + logger.dbg("MockTime:TimeVal.realtime: ", self.realtime_time) + return self.realtime_time + end + time.realtime_coarse = function() + logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime_coarse_time) + return self.realtime_coarse_time + end + time.monotonic = function() + logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic) + return self.monotonic_time + end + time.monotonic_coarse = function() + logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic) + return self.monotonic_time + end + time.boottime = function() + logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_time) + return self.boottime_time + end + time.boottime_or_realtime_coarse = function() + logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse_time) + return self.boottime_or_realtime_coarse_time + end + time.now = function() + logger.dbg("MockTime:TimeVal.now: ", self.monotonic) + return self.monotonic_time + end + + end function MockTime:uninstall() assert(self ~= nil) @@ -240,6 +310,17 @@ function MockTime:increase(value) logger.dbg("MockTime:increase (boottime) ", self.boottime) self.boottime_or_realtime_coarse = math.floor(self.boottime_or_realtime_coarse + value) logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse) + + local value_time = value * 1e6 + self.realtime_time = math.floor(self.realtime_time + value_time) + logger.dbg("MockTime:increase (realtime) ", self.realtime_time) + self.monotonic_time = math.floor(self.monotonic_time + value_time) + logger.dbg("MockTime:increase (monotonic) ", self.monotonic) + self.boottime_time = math.floor(self.boottime_time + value_time) + logger.dbg("MockTime:increase (boottime) ", self.boottime_time) + self.boottime_or_realtime_coarse_time = math.floor(self.boottime_or_realtime_coarse_time + value_time) + logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse_time) + return true end diff --git a/spec/unit/time_spec.lua b/spec/unit/time_spec.lua new file mode 100644 index 000000000..e4cf6dc18 --- /dev/null +++ b/spec/unit/time_spec.lua @@ -0,0 +1,135 @@ +describe("Time module", function() + local time, dbg, dbg_on + setup(function() + require("commonrequire") + time = require("ui/time") + dbg = require("dbg") + dbg_on = dbg.is_on + end) + + after_each(function() + if dbg_on then + dbg:turnOn() + else + dbg:turnOff() + end + end) + + it("should set", function() + local time1 = time.s(12) + local time2 = time.ms(12) + local time3 = time.us(12) + + assert.is.same(12000000, time1) + assert.is.same(12000, time2) + assert.is.same(12, time3) + end) + + it("should convert", function() + local time1 = 12000000 + local time2 = 12000 + local time3 = 12 + local time4 = time.s(12) + time.us(40) + local time5 = time.s(12) + time.us(60) + + assert.is.same(12, time.to_s(time1)) + assert.is.same(12000, time.to_ms(time1)) + assert.is.same(12000000, time.to_us(time1)) + + assert.is.same(0.012, time.to_s(time2)) + assert.is.same(12, time.to_ms(time2)) + assert.is.same(12000, time.to_us(time2)) + + assert.is.same(0.000012, time.to_s(time3)) + assert.is.same(math.floor(0.012), time.to_ms(time3)) + assert.is.same(12, time.to_us(time3)) + + assert.is.same(12.0000, time.to_number(time4)) + assert.is.same(12.0001, time.to_number(time5)) + end) + + + it("should add", function() + local time1 = time.s(5) + time.us(5000) + local time2 = time.s(10) + time.us(6000) + local time3 = time.s(10) + time.us(50000000) + + assert.is.same(time.s(15) + time.us(11000), time1 + time2) + assert.is.same(time.s(65) + time.us(5000), time1 + time3) + end) + + it("should subtract", function() + local time1 = time.s(5.005) + local time2 = time.s(10.006) + + assert.is.same(time.s(5.001), time2 - time1) + local backwards_sub = time1 - time2 + assert.is.same(time.s(-5.001), backwards_sub) + + -- Check that to/from float conversions behave, even for negative values. + assert.is.same(-5.001, time.to_number(backwards_sub)) + assert.is.same(time.s(-6) + time.us(999000), time.s(-5.001)) + + local tv = time.s(-6) + time.us(1000) + assert.is.same(-5.999, time.to_number(tv)) + assert.is.same(time.s(-6) + time.us(1000), time.s(-5.999)) + + -- We lose precision because of rounding if we go higher resolution than a ms... + tv = time.s(-6) + time.us(101) + assert.is.same(-5.9999, time.to_number(tv)) + assert.is.same(time.s(-6) + time.us(100), time.s(-5.9999)) + -- ^ precision loss + + tv = time.s(-6) + time.us(11) + assert.is.same(-6, time.to_number(tv)) + -- ^ precision loss + assert.is.same(time.s(-6) + time.us(10), time.s(-5.99999)) + -- ^ precision loss + + tv = time.s(-6) + time.us(1) + assert.is.same(-6, time.to_number(tv)) + -- ^ precision loss + assert.is.same(time.s(-6) + time.us(1), time.s(-5.999999)) + end) + + it("should derive sec and usec from more than 1 sec worth of usec", function() + local time1 = time.s(5) + time.us(5000000) + + assert.is.same(time.s(10), time1) + end) + + it("should compare", function() + local time1 = time.s(5) + time.us(5000) + local time2 = time.s(10) + time.us(6000) + local time3 = time.s(5) + time.us(5000) + local time4 = time.s(5) + time.us(6000) + + assert.is_true(time2 > time1) + assert.is_false(time2 < time1) + assert.is_true(time2 >= time1) + + assert.is_true(time4 > time1) + assert.is_false(time4 < time1) + assert.is_true(time4 >= time1) + + assert.is_true(time1 < time2) + assert.is_false(time1 > time2) + assert.is_true(time1 <= time2) + + assert.is_true(time1 == time3) + assert.is_false(time1 == time2) + assert.is_true(time1 >= time3) + assert.is_true(time1 <= time3) + end) + + it("should calculate durations", function() + local time1 = time.s(5) + time.us(500000) + local function now() return time.s(10) end + local now_save = time.now + time.now = now + assert.is.equal(time.to_s(time.s(4) + time.us(500000)), time.to_s(time.since(time1))) + assert.is.equal(time.to_ms(time.s(4) + time.us(500000)), time.to_ms(time.since(time1))) + assert.is.equal(time.to_us(time.s(4) + time.us(500000)), time.to_us(time.since(time1))) + time.now = now_save + end) +end) diff --git a/spec/unit/uimanager_bench.lua b/spec/unit/uimanager_bench.lua index bb41f16ad..fab142475 100644 --- a/spec/unit/uimanager_bench.lua +++ b/spec/unit/uimanager_bench.lua @@ -1,50 +1,54 @@ require("commonrequire") -local util = require("ffi/util") + local UIManager = require("ui/uimanager") +local time = require("ui/time") + +local NB_TESTS = 40000 local noop = function() end describe("UIManager checkTasks benchmark", function() - local now = { util.gettime() } + local now = time.now() local wait_until -- luacheck: no unused UIManager:quit() UIManager._task_queue = {} - for i=1,1000000 do + for i=1, NB_TESTS do table.insert( UIManager._task_queue, - { time = { now[1] + 10000+i, now[2] }, action = noop } + { time = now + i, action = noop, argc = 0, args = {} } ) end - -- for i=1,1000 do + for i=1, NB_TESTS do wait_until, now = UIManager:_checkTasks() -- luacheck: no unused - -- end + end end) describe("UIManager schedule benchmark", function() - local now = { util.gettime() } + local now = time.now() UIManager:quit() UIManager._task_queue = {} - for i=1,100000 do - UIManager:schedule({ now[1] + i, now[2] }, noop) + + for i=1, NB_TESTS do + UIManager:schedule(now + i, noop) end end) describe("UIManager unschedule benchmark", function() - local now = { util.gettime() } + local now = time.now() UIManager:quit() UIManager._task_queue = {} - for i=1,1000 do + for i=1, NB_TESTS do table.insert( UIManager._task_queue, - { time = { now[1] + 10000+i, now[2] }, action = 'a' } + { time = now + i, action = 'a', argc=0, args={} } ) end - for i=1,1000 do - UIManager:schedule(now, noop) + for i=1, NB_TESTS do + UIManager:schedule(now + i, noop) UIManager:unschedule(noop) end end) diff --git a/spec/unit/uimanager_spec.lua b/spec/unit/uimanager_spec.lua index 3e7eb194c..a470dbd87 100644 --- a/spec/unit/uimanager_spec.lua +++ b/spec/unit/uimanager_spec.lua @@ -1,22 +1,22 @@ describe("UIManager spec", function() - local TimeVal, UIManager + local time, UIManager local now, wait_until local noop = function() end setup(function() require("commonrequire") - TimeVal = require("ui/timeval") + time = require("ui/time") UIManager = require("ui/uimanager") end) it("should consume due tasks", function() - now = TimeVal:now() - local future = TimeVal:new{ sec = now.sec + 60000, usec = now.usec } - local future2 = TimeVal:new{ sec = future.sec + 5, usec = future.usec} + now = time.now() + local future = now + time.s(60000) + local future2 = future + time.s(5) UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, + { time = now - time.s(10), action = noop, args = {}, argc = 0 }, + { time = now - time.us(5), action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 }, { time = future, action = noop, args = {}, argc = 0 }, { time = future2, action = noop, args = {}, argc = 0 }, @@ -28,26 +28,25 @@ describe("UIManager spec", function() end) it("should calcualte wait_until properly in checkTasks routine", function() - now = TimeVal:now() - local future = TimeVal:new{ sec = now.sec + 60000, usec = now.usec } + now = time.now() + local future_time = now + time.s(60000) UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, + { time = now - time.s(10), action = noop, args = {}, argc = 0 }, + { time = now - time.us(5), action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 }, - { time = future, action = noop, args = {}, argc = 0 }, - { time = TimeVal:new{ sec = future.sec + 5, usec = future.usec }, action = noop, args = {}, argc = 0 }, + { time = future_time, action = noop, args = {}, argc = 0 }, } wait_until, now = UIManager:_checkTasks() - assert.are.same(future, wait_until) + assert.are.same(future_time, wait_until) end) it("should return nil wait_until properly in checkTasks routine", function() - now = TimeVal:now() + now = time.now() UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, + { time = now - time.s(10), action = noop, args = {}, argc = 0 }, + { time = now - time.us(5), action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 }, } wait_until, now = UIManager:_checkTasks() @@ -55,7 +54,7 @@ describe("UIManager spec", function() end) it("should insert new task properly in empty task queue", function() - now = TimeVal:now() + now = time.now() UIManager:quit() UIManager._task_queue = {} assert.are.same(0, #UIManager._task_queue) @@ -65,11 +64,11 @@ describe("UIManager spec", function() end) it("should insert new task properly in single task queue", function() - now = TimeVal:now() - local future = TimeVal:new{ sec = now.sec + 10000, usec = now.usec } + now = time.now() + local future_time = now + time.s(10000) UIManager:quit() UIManager._task_queue = { - { time = future, action = '1', args = {}, argc = 0 }, + { time = future_time, action = '1', args = {}, argc = 0 }, } assert.are.same(1, #UIManager._task_queue) UIManager:scheduleIn(150, 'quz') @@ -90,59 +89,59 @@ describe("UIManager spec", function() end) it("should insert new task in ascendant order", function() - now = TimeVal:now() + now = time.now() UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, + { time = now - time.s(10), action = '1', args = {}, argc = 0 }, + { time = now - time.us(5), action = '2', args = {}, argc = 0 }, { time = now, action = '3', args = {}, argc = 0 }, } -- insert into the tail slot UIManager:scheduleIn(10, 'foo') assert.are.same('foo', UIManager._task_queue[4].action) -- insert into the second slot - UIManager:schedule(TimeVal:new{ sec = now.sec - 5, usec = now.usec }, 'bar') + UIManager:schedule(now - time.s(5), 'bar') assert.are.same('bar', UIManager._task_queue[2].action) -- insert into the head slot - UIManager:schedule(TimeVal:new{ sec = now.sec - 15, usec = now.usec }, 'baz') + UIManager:schedule(now - time.s(15), 'baz') assert.are.same('baz', UIManager._task_queue[1].action) -- insert into the last second slot UIManager:scheduleIn(5, 'qux') assert.are.same('qux', UIManager._task_queue[6].action) -- insert into the middle slot - UIManager:schedule(TimeVal:new{ sec = now.sec, usec = now.usec - 1 }, 'quux') + UIManager:schedule(now - time.us(1), 'quux') assert.are.same('quux', UIManager._task_queue[5].action) end) it("should unschedule all the tasks with the same action", function() - now = TimeVal:now() + now = time.now() UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec - 15, usec = now.usec }, action = '3', args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 6 }, action = '3', args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, + { time = now - time.s(15), action = '3', args = {}, argc = 0 }, + { time = now - time.s(10), action = '1', args = {}, argc = 0 }, + { time = now - time.us(6), action = '3', args = {}, argc = 0 }, + { time = now - time.us(5), action = '2', args = {}, argc = 0 }, { time = now, action = '3', args = {}, argc = 0 }, } -- insert into the tail slot UIManager:unschedule('3') assert.are.same({ - { time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, + { time = now - time.s(10), action = '1', args = {}, argc = 0 }, + { time = now - time.us(5), action = '2', args = {}, argc = 0 }, }, UIManager._task_queue) end) it("should not have race between unschedule and _checkTasks", function() - now = TimeVal:now() + now = time.now() local run_count = 0 local task_to_remove = function() run_count = run_count + 1 end UIManager:quit() UIManager._task_queue = { - { time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = task_to_remove, args = {}, argc = 0 }, + { time = now - time.us(5), action = task_to_remove, args = {}, argc = 0 }, { - time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, + time = now - time.s(10), action = function() run_count = run_count + 1 UIManager:unschedule(task_to_remove)