[Lua] Non-blocking questions and coroutines

Started by Calin Leafshade, Fri 24/08/2012 10:37:51

Previous topic - Next topic

Calin Leafshade

Ok, so imagine the following scenario:

You want to ask a question of the user and blocking the returning script without actually blocking the engine. Now, I know this can probably be done with coroutines in Lua but im not sure exactly how to structure my code.

Can anyone help?

EDIT: Ok this seems to work, I seem to have solved my own problem but its a little messy i think

Code: lua

	co = coroutine.create(function()
		logmsg("run")
		QuestionScreen:Ask(co, "Yes", "No") -- this pops up a GUI asking a question
		coroutine.yield() -- this waits until the GUI input function gets its answer but does not block the engine.
		if QuestionScreen.Selected == 1 then
			PlayerAvatar:Say("Lol")
		else
			PlayerAvatar:Say("Not Lol")
		end
	end)
	coroutine.resume(co)

Denzil Quixode

#1
Here's what I would try, to make it a little neater:-

Have a self-yielding version of QuestionScreen:Ask() that expects to be called in the context of the coroutine to be suspended:
Code: Lua
function QuestionScreen:AskAsync(...)
  self:Ask(coroutine.running(), ...)
  coroutine.yield()
  return self.Selected
end


Create a utility function for running a block of code in a coroutine:
Code: Lua
function async(fn)
  coroutine.resume( coroutine.create(fn) )
end


An example of using these things together:
Code: Lua
async(function()

  logmsg("run")
  
  if QuestionScreen:AskAsync("Yes", "No") == 1 then
    PlayerAvatar:Say("Lol")
  else
    PlayerAvatar:Say("Not Lol")
  end
  
end)

Denzil Quixode

#2
Also if you'd like to make it a bit more robust by sanity-checking that self-yielding functions like QuestionScreen:AskAsync() are called from the context of an async(function() ... end) "block", you could modify the async() system a bit like this:

Code: Lua
-- weak table for identifying async coroutines without leaking memory
local async_coros = setmetatable({}, {__mode='k'})

function async(fn)
  local co = coroutine.create(fn)
  async_coros[co] = true
  coroutine.resume(co)
end

-- if called in the context of an async block, return the associated coroutine
-- if not running in an async block, throw an error
function inasync()
  local co = coroutine.running()
  assert(co and async_coros[co], 'async function called from non-async context')
  return co
end


...and then, use inasync() instead of coroutine.running() in QuestionScreen:AskAsync().

Calin Leafshade

Excellent, thats exactly what i wanted.

I wanted a way to get all the coroutine stuff away from my game logic and this does that nicely, thanks.


SMF spam blocked by CleanTalk