adds repeating tick jobs
This commit is contained in:
parent
aa4bf18e08
commit
e08f9d916e
|
@ -67,6 +67,7 @@ function mb.tap_times(key, times, ...)
|
|||
end
|
||||
|
||||
|
||||
|
||||
-- Tick mappers execute a series of tick'ered function calls, passing in each
|
||||
-- one of a sequence of things with each tick until done. So, this is like
|
||||
-- array mappers, but this time they're broken into discrete ticks. And since
|
||||
|
@ -78,31 +79,37 @@ mb.KeyJobMapper = KeyJobMapper
|
|||
|
||||
-- Creates a new tick mapper that calls a function (or a sequence of
|
||||
-- functions) on every element of an array, but only one call per tick.
|
||||
--
|
||||
-- luacheck: ignore 212/self
|
||||
function KeyJobMapper:new(func, ...)
|
||||
function KeyJobMapper:new(func, ...) -- luacheck: ignore 212/self
|
||||
-- Since we allow for sequences of functions, simply turn a single
|
||||
-- function given to us into a single-element sequence. Additionally, we
|
||||
-- need to handle the special test case where someone is giving us a
|
||||
-- function which in fact is a table (as "busted" does with spies and
|
||||
-- stubs).
|
||||
-- stubs). Bottom line: we need much more space to explain than to do our
|
||||
-- thing. Sigh.
|
||||
if type(func) == "function" or (type(func) == "table" and #func == 0) then
|
||||
func = {func}
|
||||
end
|
||||
local slf = {
|
||||
local slf = setmetatable({
|
||||
func=func, -- the function(s) to call for each element.
|
||||
fidx=1,
|
||||
flen=#func,
|
||||
elements={...}, -- the elements to map onto function calls.
|
||||
idx=1,
|
||||
len=#{...}
|
||||
}
|
||||
return setmetatable(slf, KeyJobMapper)
|
||||
}, KeyJobMapper)
|
||||
slf:reset()
|
||||
return slf
|
||||
end
|
||||
|
||||
-- Reset a tick mapper so it can be repeated (using a key repeater job).
|
||||
function KeyJobMapper:reset()
|
||||
self.fidx = 1
|
||||
self.idx = 1
|
||||
end
|
||||
|
||||
-- For each tick, process only the next element and next function in our list
|
||||
-- until all functions for this element, and then all elements have been
|
||||
-- processed. Only then indicate finish.
|
||||
-- processed. Only then declare finish by returning -1 as the "done"
|
||||
-- indication. Related, return 0 if there is still work to be done on the next
|
||||
-- round, or a positive "afterms" value indicating to call back only later.
|
||||
function KeyJobMapper:process()
|
||||
-- Don't wonder why we shield this, but this way we also correctly handle
|
||||
-- the border case of an empty list of elements to map without crashing.
|
||||
|
@ -117,10 +124,49 @@ function KeyJobMapper:process()
|
|||
-- Update index to next element and indicate back whether we'll need a
|
||||
-- further round in the future.
|
||||
self.idx = self.idx + 1
|
||||
return self.idx <= self.len
|
||||
return self.idx <= self.len and 0 or -1
|
||||
end
|
||||
-- There's always more to do, since there are more functions waiting to be called.
|
||||
return true
|
||||
-- There's always more to do, since there are more functions waiting to be
|
||||
-- called; and we want to process the next function with the next tick.
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Repeaters allow repeating tick mappers, but also repeaters; that is, we can
|
||||
-- repeat repeaters.
|
||||
local KeyJobRepeater = {}
|
||||
KeyJobRepeater.__index = KeyJobRepeater
|
||||
mb.KeyJobRepeater = KeyJobRepeater
|
||||
|
||||
-- Creates a new tick repeater that allows to repeat both tick mappers, as
|
||||
-- well as repeaters.
|
||||
function KeyJobRepeater:new(keyjob, times, pause) -- luacheck: ignore 212/self
|
||||
pause = pause or 0
|
||||
local slf = setmetatable({
|
||||
keyjob=keyjob,
|
||||
times=times,
|
||||
pause=pause
|
||||
}, KeyJobRepeater)
|
||||
slf:reset()
|
||||
return slf
|
||||
end
|
||||
|
||||
-- Rest a repeater, so repeaters can repeat repeaters. (ARGH!!!)
|
||||
function KeyJobRepeater:reset()
|
||||
self.round = 1
|
||||
end
|
||||
|
||||
function KeyJobRepeater:process()
|
||||
local afterms = self.keyjob:process()
|
||||
if afterms >= 0 then return afterms end
|
||||
-- Start the next round ... or are we done now? Please note that the next
|
||||
-- round might be delayed if so required in order to give the applications
|
||||
-- on the USB host system some time to process the burst of key events.
|
||||
-- Well, that's not meant with respect to USB and USB host, but instead
|
||||
-- the applications working on key input.
|
||||
self.round = self.round + 1
|
||||
-- Always reset, so repeater cascades work ... greetings to M.C.E.
|
||||
self.keyjob:reset()
|
||||
return self.round <= self.times and self.pause or -1
|
||||
end
|
||||
|
||||
-- Convenience function mainly for TDD: adds a set of functions to be called
|
||||
|
@ -147,7 +193,7 @@ end
|
|||
|
||||
-- Adds a sequence of key presses to the queue of tick'ed key operations,
|
||||
-- optionally enclosed by modifier presses and releases.
|
||||
function mb.send_keys(after, keys, ...)
|
||||
function mb.send_keys_repeatedly(after, times, pause, keys, ...)
|
||||
local modsno = #{...}
|
||||
-- First queue ticked modifier press(es) if necessary.
|
||||
if modsno > 0 then
|
||||
|
@ -178,3 +224,7 @@ function mb.send_keys(after, keys, ...)
|
|||
mb.send_modifiers(0, keybow.KEY_UP, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function mb.send_keys(after, keys, ...)
|
||||
mb.send_keys_repeatedly(after, 1, 0, keys, ...)
|
||||
end
|
||||
|
|
|
@ -27,9 +27,7 @@ local pq = {}
|
|||
pq.__index = pq
|
||||
|
||||
-- Creates a new priority queue.
|
||||
--
|
||||
-- luacheck: ignore 212/self
|
||||
function pq:new()
|
||||
function pq:new() -- luacheck: ignore 212/self
|
||||
return setmetatable({
|
||||
heap={},
|
||||
size=0
|
||||
|
|
|
@ -30,15 +30,18 @@ tq.__index = tq
|
|||
-- processed when ticking along. Only after the first element has finished its
|
||||
-- processing it gets removed and the next element moves into first position,
|
||||
-- getting processed next.
|
||||
--
|
||||
-- luacheck: ignore 212/self
|
||||
function tq:new()
|
||||
return setmetatable({
|
||||
head=nil,
|
||||
tail=nil,
|
||||
at=nil,
|
||||
now=0
|
||||
}, tq)
|
||||
function tq:new() -- luacheck: ignore 212/self
|
||||
local slf = setmetatable({}, tq)
|
||||
slf:clear()
|
||||
return slf
|
||||
end
|
||||
|
||||
-- Convenience for TDD
|
||||
function tq:clear()
|
||||
self.head = nil
|
||||
self.tail = nil
|
||||
self.at = nil
|
||||
self.now = 0
|
||||
end
|
||||
|
||||
-- Adds another element to this ticking queue, waiting to be processed when
|
||||
|
@ -63,21 +66,32 @@ end
|
|||
|
||||
-- Processes the head element of this ticking queue for another tick. If the
|
||||
-- head element then signals that it has finished, then the next element in
|
||||
-- queue will be processed in the next turn.
|
||||
-- queue will be processed in the next turn. Finishing is indicated by return
|
||||
-- -1 from a the process() method of the head queue element. 0 means: please
|
||||
-- call me again the next time (erm, tick). And a positive value indicates to
|
||||
-- call again, but this time after the amount of ms as indicated by the return
|
||||
-- value.
|
||||
function tq:process(now)
|
||||
self.now = now
|
||||
if self.head == nil then return end
|
||||
-- Did we already passed the time where the head element in this
|
||||
-- queue is supposed to become active?
|
||||
-- Did we already pass the time where the head element in this queue is
|
||||
-- supposed to become active or to be processed further? If not, we don't
|
||||
-- do anything yet.
|
||||
if now < self.at then return end
|
||||
if self.head:process(now) then return end
|
||||
-- The foremost element finished its processing, so let's move on to the
|
||||
-- next element in our list ... if there is any. It will start its
|
||||
-- processing with the next tick; we're just setting up things here for
|
||||
-- this next round.
|
||||
self.head = self.head.next
|
||||
if self.head then
|
||||
self.at = self.now + self.head.afterms
|
||||
local afterms = self.head:process(now)
|
||||
if afterms > 0 then
|
||||
-- More to process, but only after a certain amount of time, as
|
||||
-- indicated by the return value of the process() method.
|
||||
self.at = now + afterms
|
||||
elseif afterms < 0 then
|
||||
-- The foremost element finally finished its processing, so let's move on
|
||||
-- to the next element in our list ... if there is any. It will start its
|
||||
-- processing with the next tick; we're just setting up things here for
|
||||
-- this next round.
|
||||
self.head = self.head.next
|
||||
if self.head then
|
||||
self.at = self.now + self.head.afterms
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -72,6 +72,10 @@ describe("asynchronous keys", function()
|
|||
|
||||
local tt = require("spec/snippets/ticktock")
|
||||
|
||||
before_each(function()
|
||||
mb.keys:clear()
|
||||
end)
|
||||
|
||||
it("map a function on a ticking element sequence", function()
|
||||
local s = stub.new()
|
||||
mb.send_mapped(20, s, 1, 2, 3)
|
||||
|
@ -107,6 +111,33 @@ describe("asynchronous keys", function()
|
|||
assert.stub(s).was.called(2*3)
|
||||
end)
|
||||
|
||||
it("tick repeatedly", function()
|
||||
local s = stub.new()
|
||||
local kj = mb.KeyJobMapper:new(s, 42)
|
||||
mb.keys:add(mb.KeyJobRepeater:new(kj, 2, 20))
|
||||
|
||||
tt.ticktock(10)
|
||||
assert.stub(s).was.called(1)
|
||||
|
||||
s:clear()
|
||||
tt.ticktock(10)
|
||||
assert.stub(s).was.Not.called()
|
||||
|
||||
s:clear()
|
||||
tt.ticktock(10)
|
||||
assert.stub(s).was.called(1)
|
||||
end)
|
||||
|
||||
it("#focus tick repeatedly**2", function()
|
||||
local s = stub.new()
|
||||
local kj = mb.KeyJobMapper:new(s, 42)
|
||||
local rkj = mb.KeyJobRepeater:new(kj, 2)
|
||||
mb.keys:add(mb.KeyJobRepeater:new(rkj, 2))
|
||||
|
||||
tt.ticktock(50)
|
||||
assert.stub(s).was.called(4)
|
||||
end)
|
||||
|
||||
it("tick modifiers", function()
|
||||
local s = spy.on(keybow, "set_modifier")
|
||||
mb.send_modifiers(0, keybow.KEY_DOWN, keybow.LEFT_CTRL, keybow.LEFT_SHIFT)
|
||||
|
@ -117,7 +148,7 @@ describe("asynchronous keys", function()
|
|||
assert.spy(s).was.called.With(keybow.LEFT_SHIFT, keybow.KEY_DOWN)
|
||||
end)
|
||||
|
||||
it("ticks keys in a string", function()
|
||||
it("tick keys in a string", function()
|
||||
local sm = spy.on(keybow, "set_modifier")
|
||||
local sk = spy.on(keybow, "set_key")
|
||||
mb.send_keys(0, "abc", keybow.LEFT_CTRL, keybow.LEFT_SHIFT)
|
||||
|
@ -140,7 +171,7 @@ describe("asynchronous keys", function()
|
|||
assert.spy(sk).was.called.With("c", false)
|
||||
end)
|
||||
|
||||
it("ticks keys in a table", function()
|
||||
it("tick keys in a table", function()
|
||||
local sk = spy.on(keybow, "set_key")
|
||||
mb.send_keys(0, {"a", keybow.ENTER, "c"})
|
||||
|
||||
|
|
|
@ -36,12 +36,13 @@ end
|
|||
local El = {}
|
||||
El.__index = El
|
||||
|
||||
-- luacheck: ignore 212/self
|
||||
function El:new(stub, times)
|
||||
function El:new(stub, times, delay) -- luacheck: ignore 212/self
|
||||
times = times or 0
|
||||
delay = delay or 0
|
||||
return setmetatable({
|
||||
stub=stub,
|
||||
times=times
|
||||
times=times,
|
||||
delay=delay
|
||||
}, El)
|
||||
end
|
||||
|
||||
|
@ -49,7 +50,7 @@ end
|
|||
function El:process(t)
|
||||
if self.stub then self.stub() end
|
||||
self.times = self.times - 1
|
||||
return self.times > 0
|
||||
return self.times > 0 and self.delay or -1
|
||||
end
|
||||
|
||||
describe("ticking queue", function()
|
||||
|
@ -66,6 +67,26 @@ describe("ticking queue", function()
|
|||
assert.stub(s).was.called(2)
|
||||
end)
|
||||
|
||||
it("processes a single entry with intermediate delays", function()
|
||||
local q = tq:new()
|
||||
local s = stub.new()
|
||||
q:add(El:new(s, 2, 20), 0)
|
||||
|
||||
process(q, 10)
|
||||
assert.stub(s).was.called()
|
||||
assert.is.Not.Nil(q.head) -- element still in queue
|
||||
|
||||
s:clear()
|
||||
process(q, 10)
|
||||
assert.stub(s).was.Not.called()
|
||||
assert.is.Not.Nil(q.head) -- element still in queue
|
||||
|
||||
s:clear()
|
||||
process(q, 20)
|
||||
assert.stub(s).was.called()
|
||||
assert.is.Nil(q.head) -- element now gone for good...
|
||||
end)
|
||||
|
||||
it("processes multiple entries with intermediate delay", function()
|
||||
local q = tq:new()
|
||||
local s = stub.new()
|
||||
|
|
Loading…
Reference in New Issue