This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
The Blue daNoob's profile picture

EventSystem 1.0

Started by The Blue daNoob, 30 December 2012 - 03:22 AM
The Blue daNoob #1
Posted 30 December 2012 - 04:22 AM
Event System 1.0

This API is simply a tool that makes concurrent event handlers easier. It allows you to create and remove event handlers, pause execution until a certain event occurs, and toggle termination.

Here's an example:
Spoiler


os.loadAPI('es')

local ox = -1
local oy = -1

local function onClick(_,_,x,y)
  ox = x
  oy = y
end

local function onDrag(_,_,x,y)
  paintutils.drawLine(ox,oy,x,y,2^14)
  ox = x
  oy = y
end

local function onEnd()
  term.setBackgroundColor(2^15)
  term.clear()
  term.setCursorPos(1,1)
end

term.clear()
es.setTerminate(true)
es.addHandler('mouse_drag',onDrag)
es.addHandler('mouse_click',onClick)
es.addHandler('end',onEnd)

es.start()

Function Listing:
Spoileres.triggerEvent(eventType,[arg1,[arg2,[arg3]]]) - Trigger all functions registered as event handlers for that event type, passing the optional parameters to said function.

es.addHandler([eventType,]function) - Adds a function as a handler for the specified event type. If no type is provided, it will be set to "general," which is triggered by all "vanilla" events.

es.removeHandler(function) - Removes an event handler.

es.setTerminate(boolean) - Sets whether the program should terminate on CTRL-T.

es.waitForEvent(eventType, [arg1,[arg2,[arg3]]]) - Freezes all event handling until the specified event occurs. This can be used to pause execution of a game until a user presses the space bar.

es.start() - Begin running the event system. Note that code following this call (except in event handler functions) will not be run until exit() has been called.

es.exit() - Terminate the event system.

Besides all the "vanilla" events, there are three new events: tick, begin, and end. "Tick" is called on every in-game tick. "Begin" is called when the event system starts (useful for running code only after it has started), and "end" is called then the program is terminated. This can be either by using CTRL-T (if termination is enabled) or by calling es.exit().

It is also possible to create your own events, and then call them with es.triggerEvent().

Code:
Spoiler

	os.pullEvent = os.pullEventRaw

	local running = false

	local handlers = {}
	local handlerkeys = {}

	local eventWaiting = {}
	local terminate = true

	local function tickloop()
	  while true do
		triggerEvent('tick')
		sleep(0.05)
	  end
	end

	local function eventloop()
	  while true do
		local ev, a, b, c = os.pullEventRaw()

		if (ev == 'terminate' and terminate) or ev == 'internal-terminate' then
		  triggerEvent('end')
		  exit()
		  return
		else
		  triggerEvent(ev,a,b,c)
		  triggerEvent('general',a,b,c)
		end
	  end
	end

	function triggerEvent(ev,a,b,c)

	  if eventWaiting[1] ~= nil then
		if ev ~= eventWaiting[1] then return false end
		local argtable = {a,b,c}

		for i = 2,#eventWaiting do
		  if eventWaiting[i] == nil then break end
		  if eventWaiting[i] ~= argtable[i-1] then
			return false
		  end
		end
		eventWaiting = {}
	  end

	  if ev == nil or handlers == nil then return false end

	  if handlers[ev] == nil then return false end


	  if type(handlers[ev]) ~= 'table' and handlers[ev] ~= nil then
		handlers[ev] = nil
	  end

	  if #handlers[ev] == 0 then return false end

	  for i = 1,#handlers[ev] do
		handlers[ev][i](ev,a,b,c)
	  end

	  return true
	end

	function addHandler(etype, func)
	  if func == nil then
		func = etype
		etype = "general"
	  end

	  if type(func) ~= 'function' or type(etype) ~= 'string' then
		if (not type(func) == 'string') and (not type(etype) == 'function') then
		  return false
		end

		if type(func) == 'string' and type(etype) == 'function' then
		  local inter = func
		  func = etype
		  etype = inter
		end
	  end


	  if handlers[etype] == nil then
		handlers[etype] = {}
		table.insert(handlerkeys,etype)
	  end
	  for i = 1,#handlers[etype] do
		if func == handlers[etype][i] then
		  return false
		end
	  end
	  table.insert(handlers[etype],func)  
	  return true
	end

	function removeHandler(func)
	  while true do
		local found = false
		for i = 1,#handlerkeys do
		  local k = handlerkeys[i]
		  for j = 1,#handlers[k] do
			if handlers[k][j] == func then
			  handlers[k][j] = nil
			  found = true
			  break
			end
		  end

		  if found then break end
		end  
		if not found then return false end
	  end

	  local rmi = {}

	  for i = 1,#handlerkeys do
		local k = handlerkeys[i]
		if #handlers[k] == 0 then
		  handlers[k] = nil
		  table.insert(rmi,i)
		end
	  end
	  for i = 1,#rmi do
		table.remove(handlerkeys,rmi[i])
	  end

	end

	function exit()
	  handlers = nil
	  os.queueEvent('internal-terminate')
	end

	function waitForEvent(ev,a,b,c)
	  if ev == nil then return false end
	  eventWaiting = {ev,a,b,c}
	  return true
	end

	function start()
	  if not running then
		running = true
		triggerEvent('begin')
		parallel.waitForAny(eventloop,tickloop)
	  end
	end

	function setTerminate(bool)
	  if type(bool) ~= 'boolean' then
		return false
	  end
	  terminate = bool
	end


Pastebin

Changelog
V 1.0 :
Initial release.
Ijwu #2
Posted 31 December 2012 - 02:32 PM
This is wonderful. Thanks for this, I'm finding it very useful.
The Blue daNoob #3
Posted 01 January 2013 - 01:34 PM
It occurs to me that waitForEvent doesn't do what you would expect. I'll add another function to do what that does now and make it behave the way it should.