**This is an old revision of the document!**

Event(事件) API

Event(事件) API为用户提供了一套基本的事件处理系统,可利用其编写响应操作系统或其他程序/运行库传递的signals代码。

例如,你可以利用此API捕获按下的按键、在外接显示器连接到电脑或断开连接时进行响应,或是处理传入的网络信息。

概述

event API主要有两种用法:

  • 让程序在后台运行时能够对事件作出响应(驱动模式)。
  • 让程序在作为前台程序执行时处理事件(优先模式)。

在驱动模式下,你需要在程序中为事件注册回调(callback)函数(用 event.listen()函数),然后退出程序,以继续执行原先的程序(通常是shell)。 而在优先模式下,你无需在程序中注册事件,可以直接使用events.pull()函数进行处理。

注意:虽然从技术层面上讲可以同时使用两种工作模式,但不推荐这样做。为了保证所有进行了注册的函数都能接收到事件,事件的一次触发只有在所有函数都被调用后才算结束。因此如果你将处理函数(handler)进行了注册,又执行了拉取(pull)操作,那么同一个事件会被响应两次。

函数

  • event.listen(event: string, callback: function): boolean
    新注册一个事件侦听器,其会在触发指定事件后被调用。
    event(事件) - 需要侦听的信号名称。
    callback(回调函数) - 在收到指定信号后调用的函数。此函数将会接收注册的事件名称作为第一个参数,然后接收触发事件的信号所定义的所有参数。
    返回值:如果注册成功,数据类型为number,代表了事件id,可传递给event.cancel函数取消事件。如果函数已经注册过此类事件,将会返回false
  • event.ignore(event: string, callback: function): boolean
    注销一个先前注册的事件侦听器。
    event(事件) - 需要注销的信号名称。
    callback(回调函数) - 之前注册到此事件的回调函数。
    返回值:如果事件成功注销则返回true。如果指定的函数并未注册到此类事件则返回false
    注意:事件侦听器在注销自身时可能会返回false,此时相当于调用 event.ignore,并向其传参侦听器和侦听器所注册的函数。
  • event.timer(interval: number, callback: function[, times: number]): number
    开启一个新的定时器,在经过interval指定的时长后将会被触发。 interval(触发间隔) - 调用回调函数的间隔时长,单位为秒。可以为像0.05这样的小数。
    callback(回调函数) - 要调用的函数。
    times(次数) - 应调用函数的次数。如果省略则函数只会被调用一次。指定为math.huge可以无限次重复调用。
    返回值:定时器id,可以随时用于取消定时器。
    Note: the timer resolution can vary. If the computer is idle and enters sleep mode, it will only be woken in a game tick, so the time the callback is called may be up to 0.05 seconds off.
    - event.cancel(timerId: number): boolean
    取消某个先前由event.timer函数创建的定时器。
    timerId(定时器id) - 由event.timer函数返回的定时器id。
    返回值:如果定时器被成功终止则返回true。如果不存在指定id对应的定时器则返回false
  • event.pull([timeout: number], [name: string], ...): string, ...
    从队列中拉取下一个可用事件,或者等待可用事件出现。 timeout(超时时间) - 如果指定了值,则函数会至多等待此秒数。超时后仍未出现可用事件,则函数会返回nil
    name(事件名称) - 目标事件的模式字符串,作为事件的过滤器。如果进行了指定则函数只会返回名称符合此模式的事件。值可以为nil,此时事件名称不会被过滤。模式字符串的使用可参考string.match函数。
    - 任意数量的参数,顺序需要与目标信号所定义的相同。它们会被作为过滤器来筛选信号返回的额外参数,判断方式为是否完全相等。值可以为nil,这样将不会过滤此参数。
    样例: touch信号(在玩家点击二级或三级显示器时触发)带有如下参数: screenX: number(点击处的X坐标)、screenY: number(点击处的Y坐标)、playerName: string(进行点击的玩家姓名)。
    若要仅拉取玩家“Steve”的点击,你需要使用: local _, x, y = event.pull("touch", nil, nil, "Steve")
  • event.pullFiltered([timeout: number], [filter: function]): string, ... (自1.5.9起) 从队列中拉取并返回下一个可用事件,或者等待可用事件出现。支持以函数作为过滤器。timeout(超时时间) - 如果指定了值,则函数会至多等待此秒数。超时后仍未出现可用事件,则函数会返回nil
    filter(过滤器) - 如果指定了值,则此函数将会被作为事件的过滤器。让程序能够进行高级过滤。

    样例:

snippet.lua
local allowedPlayers = {"Kubuxu", "Sangar", "Magik6k", "Vexatos"}
local function filter(name, ...)
  if name ~= "key_up" and name ~= "key_down" and name ~= "touch" then
    return false
  end
  local nick
  if name == "touch" then
    nick = select(3, ...)
  else
    nick = select(4, ...)
  end
  for _, allowed in ipairs(allowedPlayers) do
    if nick == allowed then
      return true
    end
  end
  return false
end
 
local e = {event.pullFiltered(filter)}  --程序将会不限时等待拉取key_up(松开按键)、key_down(按下按键)和click(点击屏幕)事件。过滤器会确保只拉取allowedPlayers表中的玩家触发的事件。
  • event.pullMultiple(...): ... (自1.5.9起) pullMultiple函数可接收多个要拉取的事件名称作为参数,同时支持基本的过滤功能。
  • event.onError(message: any)
    事件回调函数的全局异常处理函数。如果有事件侦听器抛出异常,此函数将会进行处理,以避免其影响到不相关的代码(指仅调用了events.pull函数触发函数执行的代码)。默认情况下,此函数会将报错信息记录到临时文件中。 如果你想以其他方式处理事件报错,可以将此函数替换为自定义函数。
  • event.push(name: string[, ...])
    此函数仅仅为computer.pushSignal的别名,并没有对参数进行任何修改。鉴于event(事件) API库中已经有了event.pull,添加此别名会更符合逻辑。

Interrupts

Starting In OpenOS 1.6.4 and later, interrupts have been cleaned up. The following two methods are now obsolete

  • event.shouldSoftInterrupt(): boolean (Since 1.5.9 and removed in 1.6.4)
  • event.shouldInterrupt(): boolean (Since 1.5.9 and removed in 1.6.4)

Interrupts are a type of messaging intended to close or stop a process. In OpenOS the computer.pullSignal(), and thus any wrapper, generates 2 types of events.

They are especially useful when event.pull*() is called without time limit and with a filter. In some cases this means that event.pull*() could be waiting indefinitely.

  • Soft interrupts are an event signal generated by pressing Ctrl+C. The signal returns two fields, the event name "interrupted" and the computer uptime
  • Hard interrupts are generated by pressing Ctrl-Alt-C. It forcibly exits the event.pull*() method by throwing a "interrupted" error.

Basic event example

Typically user scripts care about one or two events, and don't care to handle the rest. It is good to handle “interrupted” for soft interrupts.

snippet.lua
while true do
  local id, _, x, y = event.pullMultiple("touch", "interrupted")
  if id == "interrupted" then
    print("soft interrupt, closing")
    break
  elseif id == "touch" then
    print("user clicked", x, y)
  end
end

General purpose event handler

Here is a clever solution for providing a general purpose event handler. In this example the primary functionality uses the event id returned by event.pull() as a key for a table of callbacks, using metamethods to handle undefined events. Note that event.pull puts the program on hold until there is an event available.

snippet.lua
local event = require "event" -- load event table and store the pointer to it in event
 
local char_space = string.byte(" ") -- numerical representation of the space char
local running = true -- state variable so the loop can terminate
 
function unknownEvent()
  -- do nothing if the event wasn't relevant
end
 
-- table that holds all event handlers
-- in case no match can be found returns the dummy function unknownEvent
local myEventHandlers = setmetatable({}, { __index = function() return unknownEvent end })
 
-- Example key-handler that simply sets running to false if the user hits space
function myEventHandlers.key_up(adress, char, code, playerName)
  if (char == char_space) then
    running = false
  end
end
 
-- The main event handler as function to separate eventID from the remaining arguments
function handleEvent(eventID, ...)
  if (eventID) then -- can be nil if no event was pulled for some time
    myEventHandlers[eventID](...) -- call the appropriate event handler with all remaining arguments
  end
end
 
-- main event loop which processes all events, or sleeps if there is nothing to do
while running do
  handleEvent(event.pull()) -- sleeps until an event is available, then process it
end

If you work in driver mode, you need to register the events instead, by either registering a global event handler like the one in the example above, or by registering each individual handler on its own. If you would write the example above to work in the background, the while running do loop would be replaced like this:

snippet.lua
event.listen("key_up", handleEvent) -- register handleEvent to be called on key_up then end the program

It would also be possible to register myEventHandlers.key_up directly, in which case it would receive an additional parameter (the event name) as the first parameter.

目录