adds timers that can be canceled; adds vsc workspace and task definitions; updates tests

This commit is contained in:
Harald Albrecht 2019-02-08 17:52:38 +01:00
parent 3e72a4c765
commit 5706552a3f
7 changed files with 170 additions and 37 deletions

View File

@ -11,6 +11,7 @@
},
"lua.format.lineWidth": 80,
"lua.luacheckPath": "luacheck",
"lua.preferLuaCheckErrors": true
"lua.preferLuaCheckErrors": true,
"lua.targetVersion": "5.3"
}
}

43
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,43 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"group": {
"kind": "build",
"isDefault": true
},
"type": "shell",
"command": "luacheck",
"args": ["-q", "./sdcard", "./spec"],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
}
},
{
"label": "test",
"group": {
"kind": "test",
"isDefault": true
},
"type": "shell",
"command": "busted",
"args": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
}
},
]
}

View File

@ -235,7 +235,7 @@ The file `keybow.lua` included from
testing purposes is licensed under the MIT license, as declared by Pimoroni's
keybow-firmware GitHub repository.
## Developing
## Developing/Hacking
Whether you want to dry-run your own keyboard layout or to hack Multibow: use
the unit tests which you can find in the `spec/` subdirectory. These tests
@ -248,8 +248,17 @@ Before your first testing, you'll need to run `./setup-tests.sh` once in order
to install (on Ubuntu-based distributions) the required system distribution and
LuaRocks packages.
Afterwards, simply run `./check.sh` while in the `multibow` repository root
directory to run all tests and linting.
### Visual Studio Code
There's a [Visual Studio Code](https://code.visualstudio.com/) workspace
`multibow.code-workspace` in the `.vscode/` directory inside this repository.
It defines a default build task which lints everything, as well as a default
test task which runs all tests.
### Shell
After having run `./setup-tests.sh` once, simply run `./check.sh` while in the
`multibow` repository root directory to run all tests and linting.
If you want to just test a certain file or directory, then run `busted
spec/layout/kdenlive_spec.lua` to unit test a specific keyboard layout

View File

@ -60,12 +60,13 @@ function pq:add(priority, value)
end
end
-- Removes the foremost (minimum) element from the priority queue.
-- Removes the foremost (minimum) element from the priority queue and returns
-- it as (priority, value).
function pq:remove()
if self.size == 0 then
return nil, nil
end
local pv = self.heap[1]
local min = self.heap[1]
self.heap[1] = self.heap[self.size]
self.heap[self.size] = nil
self.size = self.size - 1
@ -79,7 +80,7 @@ function pq:remove()
i = minchild
end
--
return pv.priority, pv.value
return min.priority, min.value
end
-- Returns the index for the smaller child of heap element i.
@ -103,24 +104,27 @@ function pq:search(priority, value)
return nil
end
-- Deletes all elements of (priority, value) from the priority queue.
function pq:del(priority, value, quick)
quick = quick ~= nil and quick or true
repeat
local i = pq:search(priority, value)
if i == nil then
return
-- Deletes the element (priority, value) from the priority queue. If there are
-- multiple elements of (priority, value) in the head, then only an arbitrary
-- one of them will be removed. As an indication, the element removed will be
-- returned, otherwise nil.
function pq:delete(priority, value)
local i = self:search(priority, value)
if i == nil then
return nil
end
-- reset element to lowest priority, then let it swim up, so it will be
-- removed the next time the min element is to be removed from the queue.
self.heap[i].priority = math.mininteger
while math.floor(i/2) > 0 do
local half = math.floor(i/2)
if self.heap[i].priority < self.heap[half].priority then
self.heap[i], self.heap[half] = self.heap[half], self.heap[i]
end
-- reset element to lowest priority, then let it swim up.
self.heap[i].priority = -1
while math.floor(i/2) > 0 do
local half = math.floor(i/2)
if self.heap[i].priority < self.heap[half].priority then
self.heap[i], self.heap[half] = self.heap[half], self.heap[i]
end
i = half
end
until not quick
i = half
end
local _, v = self:remove()
return priority, v
end
return pq

View File

@ -26,11 +26,49 @@ SOFTWARE.
mb.timers = mb.pq:new()
mb.now = 0
-- Private Timer class stores information about a specific timer and allows
-- applications to later cancel them.
local Timer = {}
Timer.__index = Timer
-- Cancels a timer, regardless of whether it has already been triggered, or
-- not.
function Timer:cancel()
if self.at >= 0 then
mb.timers:delete(self.at, self.timerf)
self.at = math.mininteger
end
end
-- Calls the timer's user function with its user arguments and then disables
-- this timer.
function Timer:trigger()
if self.at >= 0 then
self.at = math.mininteger
if self.timerf then
self.timerf(table.unpack(self.targs))
end
end
end
-- Returns true if the timer is still running and hasn't triggered yet;
-- otherwise, it returns false.
function Timer:isarmed()
return self.at >= 0
end
-- Activates the given timer user function after a certain amount of time has
-- passed.
function mb.after(afterms, timerf)
afterms = afterms < 0 and 0 or afterms
mb.timers:add(mb.now + afterms, timerf)
-- passed. Returns a timer object that can be used to cancel the timer.
function mb.after(afterms, timerf, ...)
local at = mb.now + (afterms < 0 and 0 or afterms)
local tim = {
timerf = timerf,
at = at,
targs = {...}
}
setmetatable(tim, Timer)
mb.timers:add(at, tim)
return tim
end
-- Tick gets called by the Keybow "firmware" every 1ms (or so). If any timers
@ -40,11 +78,11 @@ end
function tick(t)
mb.now = t
while true do
local next, timerf = mb.timers:peek()
local next, tim = mb.timers:peek()
if next == nil or t < next then
break
end
mb.timers:remove()
timerf(t)
tim:trigger()
end
end

View File

@ -55,4 +55,23 @@ describe("prioqueue", function()
assert.is.falsy(v)
end)
it("adds and removes", function()
local q = pq.new()
q:add(200, "foo")
q:add(100, "bar")
q:add(300, "zoo")
assert.is.falsy(q:delete(42, "douglas.a"))
assert.is.equal(3, q.size)
local p, v = q:delete(200, "foo")
assert.is.same({200, "foo"}, {p, v})
assert.is.equal(2, q.size)
p, v = q:remove()
assert.is.same({100, "bar"}, {p, v})
p, v = q:remove()
assert.is.same({300, "zoo"}, {p, v})
end)
end)

View File

@ -44,18 +44,21 @@ describe("multibow timers", function()
end)
it("triggers alarm at the right time", function()
local timer = spy.new(function() end)
mb.after(100, timer)
local trigger = spy.new(function() end)
local timer = mb.after(100, trigger, 1, 2, 3)
ticktock(50)
assert.spy(timer).was_not.called()
assert.spy(trigger).was_not.called()
assert.is.truthy(timer:isarmed())
ticktock(200)
assert.spy(timer).was.called(1)
assert.spy(trigger).was.called(1)
assert.spy(trigger).was.called_with(1, 2, 3)
assert.is.falsy(timer:isarmed())
timer:clear()
mb.after(-1, timer)
trigger:clear()
mb.after(-1, trigger)
ticktock(100)
assert.spy(timer).was.called(1)
assert.spy(trigger).was.called(1)
end)
it("triggers multiple alarms in correct order", function()
@ -74,4 +77,20 @@ describe("multibow timers", function()
assert.spy(t2).was.called(1)
end)
it("cancels alarms", function()
local trigger = spy.new(function() end)
local timer = mb.after(100, trigger, 1, 2, 3)
ticktock(50)
assert.is.truthy(timer:isarmed())
timer:cancel()
assert.is.falsy(timer:isarmed())
ticktock(60)
assert.spy(trigger).was_not.called()
timer:cancel()
ticktock(10)
assert.spy(trigger).was_not.called()
end)
end)