From 94d3d3b4872ee6767bb40a1b9372eb2d950bf4f8 Mon Sep 17 00:00:00 2001 From: zwim <36999612+zwim@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:03:22 +0100 Subject: [PATCH] [timeval] RIP on All Saints Day (#9686) --- frontend/ui/timeval.lua | 310 ------------------------------------- spec/unit/device_spec.lua | 8 +- spec/unit/mock_time.lua | 85 +++++----- spec/unit/timeval_spec.lua | 90 ----------- 4 files changed, 46 insertions(+), 447 deletions(-) delete mode 100644 frontend/ui/timeval.lua delete mode 100644 spec/unit/timeval_spec.lua diff --git a/frontend/ui/timeval.lua b/frontend/ui/timeval.lua deleted file mode 100644 index 39d0c29eb..000000000 --- a/frontend/ui/timeval.lua +++ /dev/null @@ -1,310 +0,0 @@ ---[[-- -A simple module to module to compare and do arithmetic with time values. - -@usage - local TimeVal = require("ui/timeval") - - local tv_start = TimeVal:now() - -- Do some stuff. - -- You can add and subtract `TimeVal` objects. - local tv_duration = TimeVal:now() - tv_start - -- And convert that object to various more human-readable formats, e.g., - print(string.format("Stuff took %.3fms", tv_duration:tomsecs())) -]] - -local ffi = require("ffi") -require("ffi/posix_h") -local logger = require("logger") -local util = require("ffi/util") - -local C = ffi.C - --- 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("TimeVal: 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("TimeVal: 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("TimeVal: BOOTTIME clock source is", HAVE_BOOTTIME and "supported" or "NOT supported") - - probe_ts = nil --luacheck: ignore -end - ---[[-- -TimeVal object. Maps to a POSIX struct timeval (). - -@table TimeVal -@int sec floored number of seconds -@int usec number of microseconds past that second. -]] -local TimeVal = { - sec = 0, - usec = 0, -} - ---[[-- -Creates a new TimeVal object. - -@usage - local timev = TimeVal:new{ - sec = 10, - usec = 10000, - } - -@treturn TimeVal -]] -function TimeVal:new(from_o) - local o = from_o or {} - if o.sec == nil then - o.sec = 0 - end - if o.usec == nil then - o.usec = 0 - elseif o.usec > 1000000 then - o.sec = o.sec + math.floor(o.usec / 1000000) - o.usec = o.usec % 1000000 - end - setmetatable(o, self) - self.__index = self - return o -end - --- Based on -function TimeVal:__lt(time_b) - if self.sec == time_b.sec then - return self.usec < time_b.usec - else - return self.sec < time_b.sec - end -end - -function TimeVal:__le(time_b) - if self.sec == time_b.sec then - return self.usec <= time_b.usec - else - return self.sec <= time_b.sec - end -end - -function TimeVal:__eq(time_b) - if self.sec == time_b.sec then - return self.usec == time_b.usec - else - return false - end -end - --- If sec is negative, time went backwards! -function TimeVal:__sub(time_b) - local diff = TimeVal:new{ sec = 0, usec = 0 } - - diff.sec = self.sec - time_b.sec - diff.usec = self.usec - time_b.usec - - if diff.usec < 0 then - diff.sec = diff.sec - 1 - diff.usec = diff.usec + 1000000 - end - - return diff -end - -function TimeVal:__add(time_b) - local sum = TimeVal:new{ sec = 0, usec = 0 } - - sum.sec = self.sec + time_b.sec - sum.usec = self.usec + time_b.usec - - if sum.usec >= 1000000 then - sum.sec = sum.sec + 1 - sum.usec = sum.usec - 1000000 - end - - return sum -end - ---[[-- -Creates a new TimeVal object based on the current wall clock time. -(e.g., gettimeofday / clock_gettime(CLOCK_REALTIME). - -This is a simple wrapper around util.gettime() to get all the niceties of a TimeVal object. -If you don't need sub-second precision, prefer os.time(). -Which means that, yes, this is a fancier POSIX Epoch ;). - -@usage - local TimeVal = require("ui/timeval") - local tv_start = TimeVal:realtime() - -- Do some stuff. - -- You can add and substract `TimeVal` objects. - local tv_duration = TimeVal:realtime() - tv_start - -@treturn TimeVal -]] -function TimeVal:realtime() - local sec, usec = util.gettime() - return TimeVal:new{ sec = sec, usec = usec } -end - ---[[-- -Creates a new TimeVal object 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 TimeVal -]] -function TimeVal:monotonic() - local timespec = ffi.new("struct timespec") - C.clock_gettime(C.CLOCK_MONOTONIC, timespec) - - -- TIMESPEC_TO_TIMEVAL - return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) } -end - ---- Ditto, but w/ CLOCK_MONOTONIC_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_MONOTONIC otherwise). -function TimeVal:monotonic_coarse() - local timespec = ffi.new("struct timespec") - C.clock_gettime(PREFERRED_MONOTONIC_CLOCKID, timespec) - - -- TIMESPEC_TO_TIMEVAL - return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) } -end - ---- Ditto, but w/ CLOCK_REALTIME_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_REALTIME otherwise). -function TimeVal:realtime_coarse() - local timespec = ffi.new("struct timespec") - C.clock_gettime(PREFERRED_REALTIME_CLOCKID, timespec) - - -- TIMESPEC_TO_TIMEVAL - return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) } -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 a TimeVal 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 TimeVal:boottime() - local timespec = ffi.new("struct timespec") - C.clock_gettime(C.CLOCK_BOOTTIME, timespec) - - -- TIMESPEC_TO_TIMEVAL - return TimeVal:new{ sec = tonumber(timespec.tv_sec), usec = math.floor(tonumber(timespec.tv_nsec / 1000)) } - end - - TimeVal.boottime_or_monotonic = TimeVal.boottime - TimeVal.boottime_or_monotonic_coarse = TimeVal.boottime - TimeVal.boottime_or_realtime = TimeVal.boottime - TimeVal.boottime_or_realtime_coarse = TimeVal.boottime -else - function TimeVal:boottime() - logger.warn("TimeVal: Attemped to call boottime on a platform where it's unsupported!") - - return TimeVal:new{ sec = 0, usec = 0 } - end - - TimeVal.boottime_or_monotonic = TimeVal.monotonic - TimeVal.boottime_or_monotonic_coarse = TimeVal.monotonic_coarse - TimeVal.boottime_or_realtime = TimeVal.realtime - TimeVal.boottime_or_realtime_coarse = TimeVal.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. -]] -TimeVal.now = TimeVal.monotonic_coarse - ---- Converts a TimeVal object to a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places) -function TimeVal:tonumber() - -- Round to 4 decimal places - return math.floor((self.sec + self.usec / 1000000) * 10000) / 10000 -end - ---- Converts a TimeVal object to a Lua (int) number (resolution: 1µs) -function TimeVal:tousecs() - return math.floor(self.sec * 1000000 + self.usec + 0.5) -end - ---[[-- Converts a TimeVal object to a Lua (int) number (resolution: 1ms). - -(Mainly useful when computing a time lapse for benchmarking purposes). -]] -function TimeVal:tomsecs() - return self:tousecs() / 1000 -end - ---- Converts a Lua (decimal) number (sec.usecs) to a TimeVal object -function TimeVal:fromnumber(seconds) - local sec = math.floor(seconds) - local usec = math.floor((seconds - sec) * 1000000 + 0.5) - return TimeVal:new{ sec = sec, usec = usec } -end - ---[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (sec.usecs variant) - -Returns a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places) (i.e., :tonumber()) -]] -function TimeVal:getDuration(start_tv) - return (TimeVal:now() - start_tv):tonumber() -end - ---[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (µs variant) - -Returns a Lua (int) number (resolution: 1µs) (i.e., :tousecs()) -]] -function TimeVal:getDurationUs(start_tv) - return (TimeVal:now() - start_tv):tousecs() -end - ---[[-- Compare a past *MONOTONIC* TimeVal object to *now*, returning the elapsed time between the two. (ms variant) - -Returns a Lua (int) number (resolution: 1ms) (i.e., :tomsecs()) -]] -function TimeVal:getDurationMs(start_tv) - return (TimeVal:now() - start_tv):tomsecs() -end - ---- Checks if a TimeVal object is positive -function TimeVal:isPositive() - return self.sec >= 0 -end - ---- Checks if a TimeVal object is zero -function TimeVal:isZero() - return self.sec == 0 and self.usec == 0 -end - ---- We often need a const TimeVal set to zero... ---- LuaJIT doesn't actually support const values (Lua 5.4+): Do *NOT* modify it. -TimeVal.zero = TimeVal:new{ sec = 0, usec = 0 } - ---- Ditto for one set to math.huge -TimeVal.huge = TimeVal:new{ sec = math.huge, usec = 0 } - -return TimeVal diff --git a/spec/unit/device_spec.lua b/spec/unit/device_spec.lua index febbae462..8247e4ae7 100644 --- a/spec/unit/device_spec.lua +++ b/spec/unit/device_spec.lua @@ -48,10 +48,10 @@ describe("device module", function() end) describe("kobo", function() - local TimeVal + local time local NickelConf setup(function() - TimeVal = require("ui/timeval") + time = require("ui/time") NickelConf = require("device/kobo/nickel_conf") end) @@ -98,13 +98,13 @@ describe("device module", function() type = C.EV_ABS, code = C.ABS_X, value = y, - time = TimeVal:realtime(), + time = time:realtime(), } local ev_y = { type = C.EV_ABS, code = C.ABS_Y, value = Screen:getWidth() - 1 - x, - time = TimeVal:realtime(), + time = time:realtime(), } kobo_dev.input:eventAdjustHook(ev_x) diff --git a/spec/unit/mock_time.lua b/spec/unit/mock_time.lua index ff4271b54..c9e7abf65 100644 --- a/spec/unit/mock_time.lua +++ b/spec/unit/mock_time.lua @@ -1,5 +1,4 @@ require("commonrequire") -local TimeVal = require("ui/timeval") local time = require("ui/time") local ffi = require("ffi") local dummy = require("ffi/posix_h") @@ -34,31 +33,31 @@ function MockTime:install() assert(self.original_util_time ~= nil) end if self.original_tv_realtime == nil then - self.original_tv_realtime = TimeVal.realtime + self.original_tv_realtime = time.realtime assert(self.original_tv_realtime ~= nil) end if self.original_tv_realtime_coarse == nil then - self.original_tv_realtime_coarse = TimeVal.realtime_coarse + self.original_tv_realtime_coarse = time.realtime_coarse assert(self.original_tv_realtime_coarse ~= nil) end if self.original_tv_monotonic == nil then - self.original_tv_monotonic = TimeVal.monotonic + self.original_tv_monotonic = time.monotonic assert(self.original_tv_monotonic ~= nil) end if self.original_tv_monotonic_coarse == nil then - self.original_tv_monotonic_coarse = TimeVal.monotonic_coarse + self.original_tv_monotonic_coarse = time.monotonic_coarse assert(self.original_tv_monotonic_coarse ~= nil) end if self.original_tv_boottime == nil then - self.original_tv_boottime = TimeVal.boottime + self.original_tv_boottime = time.boottime assert(self.original_tv_boottime ~= nil) end if self.original_tv_boottime_or_realtime_coarse == nil then - self.original_tv_boottime_or_realtime_coarse = TimeVal.boottime_or_realtime_coarse + self.original_tv_boottime_or_realtime_coarse = time.boottime_or_realtime_coarse assert(self.original_tv_boottime_or_realtime_coarse ~= nil) end if self.original_tv_now == nil then - self.original_tv_now = TimeVal.now + self.original_tv_now = time.now assert(self.original_tv_now ~= nil) end @@ -76,33 +75,33 @@ function MockTime:install() logger.dbg("MockTime:util.gettime: ", self.realtime) return self.realtime, 0 end - TimeVal.realtime = function() - logger.dbg("MockTime:TimeVal.realtime: ", self.realtime) - return TimeVal:new{ sec = self.realtime } + time.realtime = function() + logger.dbg("MockTime:Time.realtime: ", self.realtime) + return time:new{ sec = self.realtime } end - TimeVal.realtime_coarse = function() - logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime) - return TimeVal:new{ sec = self.realtime } + time.realtime_coarse = function() + logger.dbg("MockTime:Time.realtime_coarse: ", self.realtime) + return time:new{ sec = self.realtime } end - TimeVal.monotonic = function() - logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic) - return TimeVal:new{ sec = self.monotonic } + time.monotonic = function() + logger.dbg("MockTime:Time.monotonic: ", self.monotonic) + return time:new{ sec = self.monotonic } end - TimeVal.monotonic_coarse = function() - logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic) - return TimeVal:new{ sec = self.monotonic } + time.monotonic_coarse = function() + logger.dbg("MockTime:Time.monotonic_coarse: ", self.monotonic) + return time:new{ sec = self.monotonic } end - TimeVal.boottime = function() - logger.dbg("MockTime:TimeVal.boottime: ", self.boottime) - return TimeVal:new{ sec = self.boottime } + time.boottime = function() + logger.dbg("MockTime:Time.boottime: ", self.boottime) + return time:new{ sec = self.boottime } end - TimeVal.boottime_or_realtime_coarse = function() - logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse) - return TimeVal:new{ sec = self.boottime_or_realtime_coarse } + time.boottime_or_realtime_coarse = function() + logger.dbg("MockTime:Time.boottime: ", self.boottime_or_realtime_coarse) + return time:new{ sec = self.boottime_or_realtime_coarse } end - TimeVal.now = function() - logger.dbg("MockTime:TimeVal.now: ", self.monotonic) - return TimeVal:new{ sec = self.monotonic } + time.now = function() + logger.dbg("MockTime:Time.now: ", self.monotonic) + return time:new{ sec = self.monotonic } end if self.original_tv_realtime_time == nil then @@ -141,31 +140,31 @@ function MockTime:install() self.monotonic = tonumber(timespec.tv_sec) * 1e6 time.realtime = function() - logger.dbg("MockTime:TimeVal.realtime: ", self.realtime_time) + logger.dbg("MockTime:Time.realtime: ", self.realtime_time) return self.realtime_time end time.realtime_coarse = function() - logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime_coarse_time) + logger.dbg("MockTime:Time.realtime_coarse: ", self.realtime_coarse_time) return self.realtime_coarse_time end time.monotonic = function() - logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic) + logger.dbg("MockTime:Time.monotonic: ", self.monotonic) return self.monotonic_time end time.monotonic_coarse = function() - logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic) + logger.dbg("MockTime:Time.monotonic_coarse: ", self.monotonic) return self.monotonic_time end time.boottime = function() - logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_time) + logger.dbg("MockTime:Time.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) + logger.dbg("MockTime:Time.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) + logger.dbg("MockTime:Time.now: ", self.monotonic) return self.monotonic_time end @@ -177,25 +176,25 @@ function MockTime:uninstall() util.gettime = self.original_util_time end if self.original_tv_realtime ~= nil then - TimeVal.realtime = self.original_tv_realtime + time.realtime = self.original_tv_realtime end if self.original_tv_realtime_coarse ~= nil then - TimeVal.realtime_coarse = self.original_tv_realtime_coarse + time.realtime_coarse = self.original_tv_realtime_coarse end if self.original_tv_monotonic ~= nil then - TimeVal.monotonic = self.original_tv_monotonic + time.monotonic = self.original_tv_monotonic end if self.original_tv_monotonic_coarse ~= nil then - TimeVal.monotonic_coarse = self.original_tv_monotonic_coarse + time.monotonic_coarse = self.original_tv_monotonic_coarse end if self.original_tv_boottime ~= nil then - TimeVal.boottime = self.original_tv_boottime + time.boottime = self.original_tv_boottime end if self.original_tv_boottime_or_realtime_coarse ~= nil then - TimeVal.boottime_or_realtime_coarse = self.original_tv_boottime_or_realtime_coarse + time.boottime_or_realtime_coarse = self.original_tv_boottime_or_realtime_coarse end if self.original_tv_now ~= nil then - TimeVal.now = self.original_tv_now + time.now = self.original_tv_now end end diff --git a/spec/unit/timeval_spec.lua b/spec/unit/timeval_spec.lua deleted file mode 100644 index 02626714f..000000000 --- a/spec/unit/timeval_spec.lua +++ /dev/null @@ -1,90 +0,0 @@ -describe("TimeVal module", function() - local TimeVal, dbg, dbg_on - setup(function() - require("commonrequire") - TimeVal = require("ui/timeval") - 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 add", function() - local timev1 = TimeVal:new{ sec = 5, usec = 5000} - local timev2 = TimeVal:new{ sec = 10, usec = 6000} - local timev3 = TimeVal:new{ sec = 10, usec = 50000000} - - assert.is.same({sec = 15, usec = 11000}, timev1 + timev2) - assert.is.same({sec = 65, usec = 5000}, timev1 + timev3) - end) - - it("should subtract", function() - local timev1 = TimeVal:new{ sec = 5, usec = 5000} - local timev2 = TimeVal:new{ sec = 10, usec = 6000} - - assert.is.same({sec = 5, usec = 1000}, timev2 - timev1) - local backwards_sub = timev1 - timev2 - assert.is.same({sec = -6, usec = 999000}, backwards_sub) - - -- Check that to/from float conversions behave, even for negative values. - assert.is.same(-5.001, backwards_sub:tonumber()) - assert.is.same({sec = -6, usec = 999000}, TimeVal:fromnumber(-5.001)) - - local tv = TimeVal:new{ sec = -6, usec = 1000 } - assert.is.same(-5.999, tv:tonumber()) - assert.is.same({sec = -6, usec = 1000}, TimeVal:fromnumber(-5.999)) - - -- We lose precision because of rounding if we go higher resolution than a ms... - tv = TimeVal:new{ sec = -6, usec = 101 } - assert.is.same(-5.9999, tv:tonumber()) - assert.is.same({sec = -6, usec = 100}, TimeVal:fromnumber(-5.9999)) - -- ^ precision loss - - tv = TimeVal:new{ sec = -6, usec = 11 } - assert.is.same(-6, tv:tonumber()) - -- ^ precision loss - assert.is.same({sec = -6, usec = 10}, TimeVal:fromnumber(-5.99999)) - -- ^ precision loss - - tv = TimeVal:new{ sec = -6, usec = 1 } - assert.is.same(-6, tv:tonumber()) - -- ^ precision loss - assert.is.same({sec = -6, usec = 1}, TimeVal:fromnumber(-5.999999)) - end) - - it("should derive sec and usec from more than 1 sec worth of usec", function() - local timev1 = TimeVal:new{ sec = 5, usec = 5000000} - - assert.is.same({sec = 10,usec = 0}, timev1) - end) - - it("should compare", function() - local timev1 = TimeVal:new{ sec = 5, usec = 5000} - local timev2 = TimeVal:new{ sec = 10, usec = 6000} - local timev3 = TimeVal:new{ sec = 5, usec = 5000} - local timev4 = TimeVal:new{ sec = 5, usec = 6000} - - assert.is_true(timev2 > timev1) - assert.is_false(timev2 < timev1) - assert.is_true(timev2 >= timev1) - - assert.is_true(timev4 > timev1) - assert.is_false(timev4 < timev1) - assert.is_true(timev4 >= timev1) - - assert.is_true(timev1 < timev2) - assert.is_false(timev1 > timev2) - assert.is_true(timev1 <= timev2) - - assert.is_true(timev1 == timev3) - assert.is_false(timev1 == timev2) - assert.is_true(timev1 >= timev3) - assert.is_true(timev1 <= timev3) - end) -end)