adds timers that can be canceled; adds vsc workspace and task definitions; updates tests
This commit is contained in:
parent
3e72a4c765
commit
5706552a3f
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"lua.format.lineWidth": 80,
|
||||
"lua.luacheckPath": "luacheck",
|
||||
"lua.preferLuaCheckErrors": true
|
||||
"lua.preferLuaCheckErrors": true,
|
||||
"lua.targetVersion": "5.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
15
README.md
15
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue