Replace process.api with Event

This commit is contained in:
kepler155c@gmail.com 2017-07-28 19:01:59 -04:00
parent f8bcf90a6b
commit be51935662
21 changed files with 326 additions and 343 deletions

View File

@ -2,120 +2,152 @@ local Util = require('util')
local Event = {
uid = 1, -- unique id for handlers
routines = { },
handlers = { namedTimers = { } },
routines = { }, -- coroutines
types = { }, -- event handlers
timers = { }, -- named timers
terminate = false,
}
function Event.addHandler(type, f)
local event = Event.handlers[type]
local Routine = { }
function Routine:isDead()
if not self.co then
return true
end
return coroutine.status(self.co) == 'dead'
end
function Routine:terminate()
if self.co then
self:resume('terminate')
end
end
function Routine:resume(event, ...)
if not self.co then
debug(event)
debug(self)
debug(getfenv(1))
error('Cannot resume a dead routine')
end
if not self.filter or self.filter == event or event == "terminate" then
local s, m = coroutine.resume(self.co, event, ...)
if coroutine.status(self.co) == 'dead' then
self.co = nil
self.filter = nil
Event.routines[self.uid] = nil
else
self.filter = m
end
if not s and event ~= 'terminate' then
debug({s, m})
debug(self)
debug(getfenv(1))
error('\n' .. (m or 'Error processing event'))
end
return s, m
end
return true, self.filter
end
local function nextUID()
Event.uid = Event.uid + 1
return Event.uid - 1
end
function Event.on(type, fn)
local event = Event.types[type]
if not event then
event = { handlers = { } }
Event.handlers[type] = event
event = { }
Event.types[type] = event
end
local handler = {
uid = Event.uid,
uid = nextUID(),
event = type,
f = f,
fn = fn,
}
Event.uid = Event.uid + 1
event.handlers[handler.uid] = handler
event[handler.uid] = handler
setmetatable(handler, { __index = Routine })
return handler
end
function Event.removeHandler(h)
function Event.off(h)
if h and h.event then
Event.handlers[h.event].handlers[h.uid] = nil
Event.types[h.event][h.uid] = nil
end
end
function Event.queueTimedEvent(name, timeout, event, args)
Event.addNamedTimer(name, timeout, false,
function()
os.queueEvent(event, args)
local function addTimer(interval, recurring, fn)
local timerId = os.startTimer(interval)
return Event.on('timer', function(t, id)
if timerId == id then
fn(t, id)
if recurring then
timerId = os.startTimer(interval)
else
Event.off(t)
end
)
end
end)
end
function Event.addNamedTimer(name, interval, recurring, f)
function Event.onInterval(interval, fn)
return addTimer(interval, true, fn)
end
function Event.onTimeout(timeout, fn)
return addTimer(timeout, false, fn)
end
function Event.addNamedTimer(name, interval, recurring, fn)
Event.cancelNamedTimer(name)
Event.handlers.namedTimers[name] = Event.addTimer(interval, recurring, f)
end
function Event.getNamedTimer(name)
return Event.handlers.namedTimers[name]
Event.timers[name] = addTimer(interval, recurring, fn)
end
function Event.cancelNamedTimer(name)
local timer = Event.getNamedTimer(name)
local timer = Event.timers[name]
if timer then
timer.enabled = false
Event.removeHandler(timer)
Event.off(timer)
end
end
function Event.addTimer(interval, recurring, f)
local timer = Event.addHandler('timer',
function(t, id)
if t.timerId ~= id then
return
end
if t.enabled then
t.fired = true
t.cf(t, id)
end
if t.recurring then
t.fired = false
t.timerId = os.startTimer(t.interval)
else
Event.removeHandler(t)
end
end
)
timer.cf = f
timer.interval = interval
timer.recurring = recurring
timer.enabled = true
timer.timerId = os.startTimer(interval)
return timer
end
function Event.onInterval(interval, f)
return Event.addTimer(interval, true, f)
end
function Event.onTimeout(timeout, f)
return Event.addTimer(timeout, false, f)
end
function Event.waitForEvent(event, timeout)
local timerId = os.startTimer(timeout)
repeat
local e, p1, p2, p3, p4 = os.pullEvent()
if e == event then
return e, p1, p2, p3, p4
local e = { os.pullEvent() }
if e[1] == event then
return table.unpack(e)
end
until e == 'timer' and p1 == timerId
until e[1] == 'timer' and e[2] == timerId
end
function Event.addRoutine(routine)
local r = { co = coroutine.create(routine) }
local s, m = coroutine.resume(r.co)
if not s then
error(m or 'Error processing routine')
end
Event.routines[r] = true
r.filter = m
function Event.addRoutine(fn)
local r = {
co = coroutine.create(fn),
uid = nextUID()
}
setmetatable(r, { __index = Routine })
Event.routines[r.uid] = r
r:resume()
return r
end
function Event.pullEvents(...)
for _, r in ipairs({ ... }) do
Event.addRoutine(r)
for _, fn in ipairs({ ... }) do
Event.addRoutine(fn)
end
repeat
@ -128,28 +160,43 @@ function Event.exitPullEvents()
os.sleep(0)
end
local function processHandlers(e, ...)
local event = Event.types[e]
if event then
local keys = Util.keys(event)
for _,key in pairs(keys) do
local h = event[key]
if h and not h.co then
-- callbacks are single threaded (only 1 co per handler)
h.co = coroutine.create(h.fn)
Event.routines[h.uid] = h
h:resume(h, ...)
end
end
end
end
local function processRoutines(...)
local keys = Util.keys(Event.routines)
for _,key in ipairs(keys) do
local r = Event.routines[key]
if r then
r:resume(...)
end
end
end
function Event.pullEvent(eventType)
while true do
local e = { os.pullEventRaw() }
local routines = Util.keys(Event.routines)
for _, r in ipairs(routines) do
if not r.filter or r.filter == e[1] then
local s, m = coroutine.resume(r.co, table.unpack(e))
if not s and e[1] ~= 'terminate' then
debug({s, m})
debug(r)
error(m or 'Error processing event')
end
if coroutine.status(r.co) == 'dead' then
r.co = nil
Event.routines[r] = nil
else
r.filter = m
end
end
end
Event.processEvent(e)
processHandlers(table.unpack(e))
processRoutines(table.unpack(e))
if Event.terminate or e[1] == 'terminate' then
Event.terminate = false
return { 'terminate' }
@ -161,37 +208,4 @@ function Event.pullEvent(eventType)
end
end
function Event.processEvent(pe)
local e, p1, p2, p3, p4, p5 = unpack(pe)
local event = Event.handlers[e]
if event then
local keys = Util.keys(event.handlers)
for _,key in pairs(keys) do
local h = event.handlers[key]
if h and not h.co then
local co = coroutine.create(function()
h.f(h, p1, p2, p3, p4, p5)
end)
local s, m = coroutine.resume(co)
if not s then
debug({s, m})
debug(h)
error(m or 'Error processing ' .. e)
elseif coroutine.status(co) ~= 'dead' then
h.co = co
h.filter = m
Event.routines[h] = true
end
end
end
end
return e, p1, p2, p3, p4, p5
end
Event.on = Event.addHandler
Event.off = Event.removeHandler
return Event

View File

@ -18,7 +18,7 @@ if device and device.wireless_modem then
Message.enable()
end
Event.addHandler('device_attach', function(event, deviceName)
Event.on('device_attach', function(event, deviceName)
if deviceName == 'wireless_modem' then
Message.enable()
end
@ -41,7 +41,7 @@ function Message.removeHandler(h)
end
end
Event.addHandler('modem_message',
Event.on('modem_message',
function(event, side, sendChannel, replyChannel, msg, distance)
if msg and msg.type then -- filter out messages from other systems
local id = replyChannel

View File

@ -66,8 +66,20 @@ function Manager:init(args)
local shift = false
local mouseDragged = false
local pages = { }
local running = false
Event.on('term_resize', function(h, side)
-- single thread all input events
local function singleThread(event, fn)
Event.on(event, function(...)
if not running then
running = true
fn(...)
running = false
end
end)
end
singleThread('term_resize', function(h, side)
if self.currentPage then
-- the parent doesn't have any children set...
-- that's why we have to resize both the parent and the current page
@ -81,7 +93,7 @@ function Manager:init(args)
end
end)
Event.on('mouse_scroll', function(h, direction, x, y)
singleThread('mouse_scroll', function(h, direction, x, y)
if self.target then
local event = self:pointToChild(self.target, x, y)
local directions = {
@ -97,7 +109,7 @@ function Manager:init(args)
end)
-- this should be moved to the device !
Event.on('monitor_touch', function(h, side, x, y)
singleThread('monitor_touch', function(h, side, x, y)
if self.currentPage then
if self.currentPage.parent.device.side == side then
self:click(1, x, y)
@ -105,7 +117,7 @@ function Manager:init(args)
end
end)
Event.on('mouse_click', function(h, button, x, y)
singleThread('mouse_click', function(h, button, x, y)
mouseDragged = false
if button == 1 and shift and control then -- debug hack
@ -123,7 +135,7 @@ function Manager:init(args)
end
end)
Event.on('mouse_up', function(h, button, x, y)
singleThread('mouse_up', function(h, button, x, y)
if self.currentPage and not mouseDragged then
if not self.currentPage.parent.device.side then
@ -132,7 +144,7 @@ function Manager:init(args)
end
end)
Event.on('mouse_drag', function(h, button, x, y)
singleThread('mouse_drag', function(h, button, x, y)
mouseDragged = true
if self.target then
@ -146,7 +158,7 @@ function Manager:init(args)
end
end)
Event.on('paste', function(h, text)
singleThread('paste', function(h, text)
if clipboard.isInternal() then
text = clipboard.getData()
end
@ -156,7 +168,7 @@ function Manager:init(args)
end
end)
Event.on('char', function(h, ch)
singleThread('char', function(h, ch)
control = false
if self.currentPage then
self:inputEvent(self.currentPage.focused, { type = 'key', key = ch })
@ -164,7 +176,7 @@ function Manager:init(args)
end
end)
Event.on('key_up', function(h, code)
singleThread('key_up', function(h, code)
if code == keys.leftCtrl or code == keys.rightCtrl then
control = false
elseif code == keys.leftShift or code == keys.rightShift then
@ -172,7 +184,7 @@ function Manager:init(args)
end
end)
Event.on('key', function(h, code)
singleThread('key', function(h, code)
local ch = keys.getName(code)
if not ch then
return
@ -337,6 +349,9 @@ function Manager:click(button, x, y)
button = 3
self.doubleClickTimer = nil
else
if self.doubleClickTimer then
debug(c - self.doubleClickTimer)
end
self.doubleClickTimer = c
self.doubleClickX = x
self.doubleClickY = y

View File

@ -103,7 +103,7 @@ function page.grid:draw()
UI.Grid.draw(self)
end
function eventLoop()
Event.addRoutine(function()
while true do
local e = { os.pullEvent() }
@ -124,8 +124,7 @@ function eventLoop()
page:sync()
end
end
end
end)
UI:setPage(page)
Event.pullEvents(eventLoop)
UI.term:reset()
UI:pullEvents()

View File

@ -50,9 +50,11 @@ function page:eventHandler(event)
Event.exitPullEvents()
elseif event.type == 'key' and event.key == 'enter' then
if self.grid:getSelected() then
showHelp(self.grid:getSelected().name)
self:setFocus(self.filter)
self:draw()
end
elseif event.type == 'grid_select' then
showHelp(event.selected.name)
@ -80,5 +82,4 @@ function page:eventHandler(event)
end
UI:setPage(page)
Event.pullEvents()
UI.term:reset()
UI:pullEvents()

View File

@ -141,5 +141,4 @@ if not device.wireless_modem then
end
UI:setPage(page)
Event.pullEvents()
UI.term:reset()
UI:pullEvents()

View File

@ -476,7 +476,7 @@ UI:setPages({
main = page,
})
Event.addHandler('os_register_app', function()
Event.on('os_register_app', function()
loadApplications()
page:refresh()
page:draw()

View File

@ -201,11 +201,11 @@ function methodsPage.viewportConsole:draw()
c.ymax = c.cursorY + 1
end
Event.addHandler('peripheral', function()
Event.on('peripheral', function()
peripheralsPage:updatePeripherals()
end)
Event.addHandler('peripheral_detach', function()
Event.on('peripheral_detach', function()
peripheralsPage:updatePeripherals()
end)
@ -215,5 +215,4 @@ UI:setPages({
methods = methodsPage,
})
Event.pullEvents()
UI.term:reset()
UI:pullEvents()

View File

@ -62,12 +62,11 @@ function page.grid:getDisplayValues(row)
return row
end
Event.addTimer(1, true, function()
Event.onInterval(1, function()
page.grid:update()
page.grid:draw()
page:sync()
end)
UI:setPage(page)
Event.pullEvents()
UI.term:reset()
UI:pullEvents()

View File

@ -1,4 +1,5 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Socket = require('socket')
local Terminal = require('terminal')
@ -320,20 +321,6 @@ function page:enable()
-- self.tabs:activateTab(page.tabs.turtles)
end
local function updateThread()
while true do
if page.turtle then
local t = _G.network[page.turtle.id]
page.turtle = t
page:draw()
page:sync()
end
os.sleep(1)
end
end
if not Util.getOptions(options, { ... }, true) then
return
end
@ -348,9 +335,17 @@ if options.turtle.value >= 0 then
end
end
Event.onInterval(1, function()
if page.turtle then
local t = _G.network[page.turtle.id]
page.turtle = t
page:draw()
page:sync()
end
end)
UI:setPage(page)
page.tabs:activateTab(page.tabs[options.tab.value])
UI:pullEvents(updateThread)
UI.term:reset()
UI:pullEvents()

View File

@ -475,7 +475,7 @@ function Builder:getSupplies()
return t
end
Event.addHandler('build', function()
Event.on('build', function()
Builder:build()
end)

View File

@ -999,10 +999,8 @@ jobMonitor()
jobListGrid:draw()
jobListGrid:sync()
local function craftingThread()
Event.onInterval(5, function()
while true do
os.sleep(5)
if not craftingPaused then
local items = chestProvider:listItems()
if Util.size(items) == 0 then
@ -1023,8 +1021,7 @@ local function craftingThread()
craftItems(craftList, items)
end
end
end
end
end)
UI:pullEvents(craftingThread)
UI:pullEvents()
jobListGrid.parent:reset()

View File

@ -4,7 +4,7 @@ local Socket = require('socket')
local MEProvider = require('meProvider')
local Logger = require('logger')
local Point = require('point')
local process = require('process')
local Event = require('event')
if not device.wireless_modem then
error('Modem is required')
@ -16,14 +16,6 @@ if not turtle then
error('Can only be run on a turtle')
end
turtle.clearMoveCallback()
local gps = GPS.getPointAndHeading()
if not gps then
error('could not get gps location')
end
turtle.setPoint(gps)
local blocks = { }
local meProvider = MEProvider()
local items = { }
@ -50,7 +42,7 @@ turtle.setMoveCallback(function(action, pt)
for _,slot in pairs(slots) do
if turtle.getItemCount(slot.index) ~= slot.qty then
printError('Slots changed')
process:terminate()
Event.exitPullEvents()
end
end
end
@ -109,7 +101,8 @@ function gotoPoint(pt, doDetect)
end
if doDetect and not turtle.detectDown() then
error('Missing target')
printError('Missing target')
Event.exitPullEvents()
end
end
@ -254,14 +247,14 @@ local function pickupHost(socket)
end
end
process:newThread('pickup', function()
Event.addRoutine(function()
while true do
print('waiting for connection on port 5222')
local socket = Socket.server(5222)
print('pickup: connection from ' .. socket.dhost)
process:newThread('pickup_connection', function() pickupHost(socket) end)
Event.addRoutine(function() pickupHost(socket) end)
end
end)
@ -304,10 +297,7 @@ local function eachClosestEntry(t, fn)
end
end
refuel()
turtle.abort = false
local deliveryThread = process:newThread('deliveries', function()
Event.addRoutine(function()
while true do
if chestPt then
@ -327,17 +317,17 @@ local deliveryThread = process:newThread('deliveries', function()
end
os.sleep(60)
end
Event.exitPullEvents()
end)
turtle.run(function()
while true do
local e = process:pullEvent()
if e == 'terminate' or deliveryThread:isDead() then
break
end
if not turtle.enableGPS() then
error('turtle: No GPS found')
end
refuel()
Event.pullEvents()
end)
process:threadEvent('terminate')

View File

@ -612,7 +612,7 @@ while not bExit do
term.setTextColour(_colors.textColor)
if #sLine > 0 then
local result, err = shell.run( sLine )
if not result then
if not result and err then
printError(err)
end
end

View File

@ -164,11 +164,10 @@ function changedPage:refresh()
self.grid:draw()
end
Event.addTimer(5, true, function()
Event.onInterval(5, function()
changedPage:refresh()
changedPage:sync()
end)
UI:setPage(changedPage)
UI:pullEvents()
UI.term:reset()

View File

@ -375,7 +375,7 @@ Message.addHandler('finished',
Builder:finish()
end)
Event.addHandler('turtle_abort',
Event.on('turtle_abort',
function()
turtle.abort = false
turtle.status = 'aborting'

View File

@ -30,7 +30,6 @@ end
local w, h = ct.getSize()
socket:write({
type = 'termInfo',
width = w,
height = h,
isColor = ct.isColor(),
@ -70,7 +69,7 @@ while true do
if filter[event] then
if not socket:write({ type = 'shellRemote', event = e }) then
if not socket:write(e) then
socket:close()
break
end

View File

@ -1,4 +1,4 @@
print('\nStarting multishell..')
print('\nStarting Opus..')
LUA_PATH = '/sys/apis'

View File

@ -3,7 +3,7 @@ local function follow(id)
require = requireInjector(getfenv(1))
local Socket = require('socket')
local Point = require('point')
local process = require('process')
local Event = require('event')
turtle.status = 'follow ' .. id
@ -20,8 +20,38 @@ local function follow(id)
local lastPoint
local following = false
local followThread = process:newThread('follower', function()
while true do
Event.on('turtle_follow', function(_, pt)
local pts = {
{ x = pt.x + 2, z = pt.z, y = pt.y },
{ x = pt.x - 2, z = pt.z, y = pt.y },
{ x = pt.x, z = pt.z + 2, y = pt.y },
{ x = pt.x, z = pt.z - 2, y = pt.y },
}
local cpt = Point.closest(turtle.point, pts)
local blocks = { }
local function addBlocks(tpt)
table.insert(blocks, tpt)
local apts = Point.adjacentPoints(tpt)
for _,apt in pairs(apts) do
table.insert(blocks, apt)
end
end
-- don't run into player
addBlocks(pt)
addBlocks({ x = pt.x, z = pt.z, y = pt.y + 1 })
if turtle.pathfind(cpt, blocks) then
turtle.headTowards(pt)
end
following = false
end)
Event.onInterval(.5, function()
local function getRemotePoint()
if not turtle.abort then
@ -60,50 +90,16 @@ local function follow(id)
if d < Point.pythagoreanDistance(turtle.point, pt) + 10 then
lastPoint = Point.copy(pt)
following = true
process:newThread('turtle_follow', function()
local pts = {
{ x = pt.x + 2, z = pt.z, y = pt.y },
{ x = pt.x - 2, z = pt.z, y = pt.y },
{ x = pt.x, z = pt.z + 2, y = pt.y },
{ x = pt.x, z = pt.z - 2, y = pt.y },
}
local cpt = Point.closest(turtle.point, pts)
local blocks = { }
local function addBlocks(tpt)
table.insert(blocks, tpt)
local apts = Point.adjacentPoints(tpt)
for _,apt in pairs(apts) do
table.insert(blocks, apt)
os.queueEvent('turtle_follow', pt)
end
end
-- don't run into player
addBlocks(pt)
addBlocks({ x = pt.x, z = pt.z, y = pt.y + 1 })
if turtle.pathfind(cpt, blocks) then
turtle.headTowards(pt)
end
following = false
end)
end
end
os.sleep(.5)
end
end)
while true do
local e = process:pullEvent()
if e == 'terminate' or followThread:isDead() or e =='turtle_abort' then
process:threadEvent('terminate')
break
end
end
Event.on('turtle_abort', function()
Event.exitPullEvents()
end)
Event.pullEvents()
socket:close()
@ -114,4 +110,3 @@ local s, m = turtle.run(function() follow({COMPUTER_ID}) end)
if not s and m then
error(m)
end

View File

@ -1,7 +1,10 @@
local Socket = require('socket')
local process = require('process')
local function wrapTerm(socket, termInfo)
local function telnetHost(socket, termInfo)
require = requireInjector(getfenv(1))
local Event = require('event')
local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',
'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', }
@ -11,10 +14,15 @@ local function wrapTerm(socket, termInfo)
for _,k in pairs(methods) do
socket.term[k] = function(...)
if not socket.queue then
socket.queue = { }
os.startTimer(0)
Event.onTimeout(0, function()
socket:write(socket.queue)
socket.queue = nil
end)
end
table.insert(socket.queue, {
f = k,
args = { ... },
@ -26,54 +34,28 @@ local function wrapTerm(socket, termInfo)
socket.term.getSize = function()
return termInfo.width, termInfo.height
end
end
local function telnetHost(socket, termInfo)
require = requireInjector(getfenv(1))
local process = require('process')
wrapTerm(socket, termInfo)
local shellThread = process:newThread('shell_wrapper', function()
local shellThread = Event.addRoutine(function()
os.run(getfenv(1), 'sys/apps/shell')
socket:close()
Event.exitPullEvents()
end)
process:newThread('telnet_read', function()
Event.addRoutine(function()
while true do
local data = socket:read()
if not data then
Event.exitPullEvents()
break
end
if data.type == 'shellRemote' then
local event = table.remove(data.event, 1)
shellThread:resume(event, unpack(data.event))
end
shellThread:resume(table.unpack(data))
end
end)
while true do
local e = process:pullEvent('timer')
if e == 'terminate' then
break
end
if not socket.connected then
break
end
if socket.queue then
if not socket:write(socket.queue) then
print('telnet: connection lost to ' .. socket.dhost)
break
end
socket.queue = nil
end
end
Event.pullEvents()
socket:close()
process:threadEvent('terminate')
shellThread:terminate()
end
process:newThread('telnet_server', function()