I was try to use coroutine in lua, I tried the code below, repl.it here https://repl.it/repls/WordyWonderfulVisitor and it does not print the list content in the loop.
local list = {1,2,3};
local function iter()
for i, v in ipairs(list) do
print(i, v)
coroutine.yield();
end
end
local co = coroutine.create(iter);
coroutine.resume(co);
coroutine.resume(co);
-- iter();
what's wrong with my code ?
There is nothing wrong with your code. It prints 1 1 and 2 2 as expected and the results are the same in Lua 5.1-5.4 versions.
If you want to see one more result 3 3, then you need to call resume one more time. You can also check the status of the coroutine by using coroutine.status, so after the first two executions you'll get "suspended" and after the execution is completed you'll get "dead".
Related
I am learning lua by this video tutorial, it has this piece of code:
co = coroutine.create(function()
for i=1,5 do
print(coroutine.yield(i))
end
end)
print(coroutine.resume(co,1,2))
print(coroutine.resume(co,3,4))
print(coroutine.resume(co,5,6))
print(coroutine.resume(co,7,8))
print(coroutine.resume(co,9,10))
print(coroutine.resume(co,11,12))
The output is like this:
true 1
3 4
true 2
5 6
true 3
7 8
true 4
9 10
true 5
11 12
true
But I don't understand how yield and resume passes parameters to each other and why yield doesn't output the first 1,2 that resume passes to it, could someone please explain? Thanks
Normal Lua functions have one entry (where arguments get passed in) and one exit (where return values are passed out):
local function f( a, b )
print( "arguments", a, b )
return "I'm", "done"
end
print( "f returned", f( 1, 2 ) )
--> arguments 1 2
--> f returned I'm done
The arguments are bound to the parameter names (local variables) by putting them inside the parentheses, the return values listed as part of the return statement can be retrieved by putting the function call expression in the right-hand side of an assignment statement, or inside of a larger expression (e.g. another function call).
There are alternative ways to call a function. E.g. pcall() calls a function and catches any runtime errors that may be raised inside. Arguments are passed in by putting them as arguments into the pcall() function call (right after the function itself). pcall() also prepends an additional return value that indicates whether the function exited normally or via an error. The inside of the called function is unchanged.
print( "f returned", pcall( f, 1, 2 ) )
--> arguments 1 2
--> f returned true I'm done
You can call the main function of a coroutine by using coroutine.resume() instead of pcall(). The way the arguments are passed, and the extra return value stays the same:
local th = coroutine.create( f )
print( "f returns", coroutine.resume( th, 1, 2 ) )
--> arguments 1 2
--> f returns true I'm done
But with coroutines you get another way to (temporarily) exit the function: coroutine.yield(). You can pass values out via coroutine.yield() by putting them as arguments into the yield() function call. Those values can be retrieved outside as return values of the coroutine.resume() call instead of the normal return values.
However, you can re-enter the yielded coroutine by again calling coroutine.resume(). The coroutine continues where it left off, and the extra values passed to coroutine.resume() are available as return values of the yield() function call that suspended the coroutine before.
local function g( a, b )
print( "arguments", a, b )
local c, d = coroutine.yield( "a" )
print( "yield returned", c, d )
return "I'm", "done"
end
local th = coroutine.create( g )
print( "g yielded", coroutine.resume( th, 1, 2 ) )
print( "g returned", coroutine.resume( th, 3, 4 ) )
--> arguments 1 2
--> g yielded true a
--> yield returned 3 4
--> g returned true I'm done
Note that the yield need not be directly in the main function of the coroutine, it can be in a nested function call. The execution jumps back to the coroutine.resume() that (re-)started the coroutine in the first place.
Now to your question why the 1, 2 from the first resume() doesn't appear in your output: Your coroutine main function doesn't list any parameters and so ignores all arguments that are passed to it (on first function entry). On a similar note, since your main function doesn't return any return values, the last resume() doesn't return any extra return values besides the true that indicates successful execution as well.
co = coroutine.create(function()
for i=1,5 do
print(coroutine.yield(i))
end
end)
We start the coroutine the first time using:
print(coroutine.resume(co,1,2))
it will run until the first yield. our first resume call will return true and the parameters of yield (here i = 1) which explains the first output line.
our coroutine is now suspended. once we call resume a second time:
print(coroutine.resume(co,3,4))
your first yield finally returns and the parameters of your current resume (3,4) will be printed. the for loops second iteration begins, coroutine.yield(2) is called, supending the coroutine which again will make your last resume return true, 2 and so on
So actually in your example coroutine.resume(co) would be sufficient for the first call as any further arguments are lost anyway.
The reason we see this behavior is subtle, but it has to do with a mismatch of "entering" and "exiting" yield statements. It also has to do with the order in which print and yield are called within your anonymous function.
Let's imagine a graph of the execution of print(coroutine.yield(i)) vs. iteration.
On the first iteration, we have coroutine.resume pass 1 and 2 to the coroutine. This is the origin point, so we are not picking up from a former yield, but rather the original call of the anonymous function itself. The yield is called inside the print, returning i=1 but leaving print uncalled. The function exits.
What follows is the suspension of the function for a time before we see resumption by the next coroutine.resume.This resume passes 3 and 4. The function picks up at the last yield. Remember that the print function was left uncalled as the 1st yield was called first and exited the program? Well, execution returns inside the print, and so print now gets called, but this time returning 3 and 4 as these were the latest values to be transferred over. The function repeats again, calling yield before print, returning i=2.
If we go down the iterations we will more definitely the pattern underlying why we didn't see the 1 and 2. Our first iteration was an "exiting" yield unpaired with a corresponding "entering yield." This corresponds to the 1st time execution of the coroutine co.
We might expect the last yield to also go unpaired, but the difference is that we'll have an "entering" yield which is unpaired as opposed to an "exiting" yield which is unpaired. This is because the loop would have already finished. This explains why we see 11 12 followed by true followed with no "exiting" yield return.
This sort of situation is independent of the parity (even/oddness) of the for loop inside. What only matters are the resume and yield pairs and the manner in which they are handled. You have to appreciate that yield won't return a value within the function on the first call of resume which is being used to call the function inside the coroutine in the first place.
I am trying to use the redis-lua library within copas. It requires some patching.
One problem is that redis-lua defines some iterators as coroutines, but these iterators perform network operations that can yield.
So, coroutine.yield is used for two very different things: for the iterator, and for copas. As network calls are nested within iterators, network yields are intercepted by the coroutine.wrap of the iterator, instead of being intercepted by copas.
The example below shows the problem :
local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield () end -- network yield
coroutine.yield () -- iterator yield
end
end
local citerator = coroutine.wrap (iterator)
local function loop () -- use of the iterator within a copas thread
while citerator () do end
end
local cloop = coroutine.create (loop)
while coroutine.resume (cloop) do end -- same as copas loop, executes the cloop thread
Is there a "standard" solution to this problem, still allowing to use coroutines for iterators?
I was able to make a small example work by "tagging" the yields (see below), but it is incompatible with existing code. I can leave the copas code unmodified, but have to update iterators in redis-lua.
local function wrap (f, my_tag)
-- same as coroutine.wrap, but uses my_tag to yield again
local co = coroutine.create (f)
return function ()
local t = table.pack (coroutine.resume (co))
local code = t [1]
local tag = t [2]
table.remove (t, 1)
table.remove (t, 1)
if tag == nil then
return
elseif my_tag == tag then
return table.unpack (t)
else
coroutine.yield (tag, table.unpack (t))
end
end
end
local Iterator = {} -- tag for iterator yields
local Network = {} -- tag for network yields
local function iterator ()
for i = 1, 2 do
if i == 2 then coroutine.yield (Network, i) end
coroutine.yield (Iterator, i)
end
end
local citerator = wrap (iterator, Iterator)
local function loop ()
while citerator () do end
end
local cloop = wrap (loop, Network)
while cloop () do end
Is there a better solution?
Lua coroutine always yield to the last thread they were resumed from. The Copas socket functions expect to yield back to the Copas event loop, but instead they get stuck with the coroutine used to implement the redis-lua iterators. Unfortunately there isn't much you can do to fix that except changing the code of the redis-lua iterators. The reason why no one has done so yet is that until Lua 5.2 (LuaJIT can do it as well) it wasn't even possible to yield from an iterator function (the iterator yields in redis-lua work fine because they never leave the iterator function, but you cannot yield beyond the for loop like the Copas socket functions would try to do).
Your idea about using a tag value for distinguishing the iterator yields from the rest is good. You just have to make sure that you pass all yields not intended for the iterator function to the coroutine one level up, including any arguments/return values of coroutine.yield and coroutine.resume (the later is implicit when calling the coroutine.wraped function).
More specifically, if you have code like this in redis-lua:
-- ...
return coroutine.wrap( function()
-- ...
while true do
-- ...
coroutine.yield( some_values )
end
end )
You change it to:
-- ...
local co_func = coroutine.wrap( function()
-- ...
while true do
-- ...
coroutine.yield( ITERATOR_TAG, some_values ) -- mark all iterator yields
end
return ITERATOR_TAG -- returns are also intended for the iterator
end )
return function()
return pass_yields( co_func, co_func() ) -- initial resume of the iterator
end
The ITERATOR_TAG and the pass_yields function go somewhere near the top of redis.lua:
local ITERATOR_TAG = {} -- unique value to mark yields/returns
local function pass_yields( co_func, ... )
if ... == ITERATOR_TAG then -- yield (or return) intended for iterator?
return select( 2, ... ) -- strip the ITERATOR_TAG from results and return
else
-- pass other yields/resumes back and forth until we hit another iterator
-- yield (or return); using tail recursion here instead of a loop makes
-- handling vararg lists easier.
return pass_yields( co_func, co_func( coroutine.yield( ... ) ) )
end
end
AFAIK, the redis-lua developers are planning to tag another release by the end of the year, so they probably will be grateful for pull requests.
In your first example, you are using wrap(loop). I suppose this is copas' wrap, because there is no reference to copas in this code...
However, you are supposed to copas.wrap() a socket, but your loop is a function!
See copas documentation for a good introduction.
function findWord(s,i)
-- find first word in given text
local j = i+1
while not _isWhite(s:byte(j)) and j < #s do -- getting error here
j = j + 1
end
return s:sub(i,j), j
end
function splitText(s,maxLen)
-- split text into chunks of maxLen length
rs ={}
local function _g(s,i,c,rs)
-- recursively split text
local function _f(s,i,c)
-- recursively find words and add each word to a chunk
local w,i = findWord(s,i)
if i == #s then return c..w end
if #(c..w) <= maxLen then
c = c..w
s = s:sub(i+1,#s,true)
return _f(s,1,c)
else
return c
end
end
rs[#rs+1] = _f(s,1,'')
i = i+#rs[#rs]
if i < #s then
local s = s:sub(i,#s,true)
return _g(s,1,'',rs)
else
return rs
end
end
return _g(s,1,'',rs)
end
I have above function to split a string, It has been working earlier but this time it started giving error "call stack has exceeded maximum of depth of 100, verify a function is not calling itself by accident."
Any idea why I might be getting this error, this behaviour seems random since I am quite sure about rest of the script and same split function has been working fine as well.
EDIT:
Yes, isWhiteSpace was provided to me and has the following code, I am not supposed to change it since it worked earlier . Here is isWhite function:
function _isWhite(byte)
return byte == 32 or byte == 9
end
So both _g and _f call themselves, and _g calls _f. So clearly the recursion-stop conditions you have are too weak. In _g I see
if i < #s then
local s = ...
return _g(s,1,'',rs)
else
return rs
end
which will stop when i >= #s. If this never happens, you will get infinite recursion. It is hard to say by looking at the code how i varies, but based on this line:
i = i+#rs[#rs]
it appears to by some value, but can't say if there is guarantee that stop condition will ever be reached. With _f it is worse: the stop recursion conditions are
if i == #s then return c..w end
and
#(c..w) > maxLen
Again very hard to say if this is strong enough: what if i is greater than #s, does the rest of the function work? Although findWord() returns i<#s for non empty s, not sure what will happen if s empty.
Best way to find out is to put some print statements that give you a trace of _g and _f and the parameters received, this will tell you clearly what stop conditions are being missed.
I'm currently working on a simple 'guess the number' game using Lua. I'm programming through an app on my iPad called TouchLua+. One of the game modes is you have a certain amount of time to guess the number. I thought that to do this, I would create a coroutine that counts down from the given time. For some reason, I can't input a number while the coroutine is running. Can anyone help? Here is what I have so far.
target = math.random(1, 100)
coroutine.resume(coroutine.create(function()
for i = 1, roundTime do
sleep(1000)
sys.alert("tock")
end
lose = true
coroutine.yield()
end))
repeat
local n = tonumber(io.read())
if (n > target) then
print("Try a lower number.\n")
elseif (n < target) then
print("Try a higher number.\n")
else
win = true
end
until (lose or win)
return true
Coroutines are not a form of multiprocessing, they are a form of cooperative multithreading. As such, while the coroutine is running, nothing else is running. A coroutine is meant to yield control back to the caller often, and the caller is meant to resume the coroutine so the coroutine can continue where it yielded. You can see how this will appear to be parallel processing.
So in your case you would want to yield from inside the loop, after a small sleep time:
co = coroutine.create(function()
for i = 1, roundTime do
sleep(1)
sys.alert("tock")
coroutine.yield()
end
lose = true
end)
Unfortunately, you can't interrupt io.read(), which means the above is of no use. Ideally you would want a "io.peek" function so you could do the following:
while coroutine.status(co) ~= "dead" do
coroutine.resume(co)
if io.peek() then -- non-blocking, just checks if a key has been pressed
... get the answer and process ...
end
end
I am not aware of a non-blocking keyboard IO in Lua. You could create a C extension that exposes some of C non-blocking keyboard input to Lua, assuming that TouchLua+ supports C extensions. I doubt it, given that it is an iOS app.
It doesn't appear that there is a time loop or callbacks or such, and couldn't find docs. If you have option of creating a text box where user can enter answer and they have to click accept then you can measure how long it took. If there is a time loop you could check time there and show message if run out of time. All this is very easy to do in Corona, maybe not possible in TouchLua+.
I'm currently working on a game using Roblox (which uses Lua). It is a basically made up of several minigames. At the beginning of each round, all the players in game are put in a table and teleported to an area. That is where the coroutine comes into play. As the round is in progress, I want a coroutine to start. Every second that coroutine checks if the player's health is below zero, and removes them from the currentPlayer table if it is.
Sorry if I am not describing the problem correctly, but the coroutine will not yield. I haven't used coroutines before, so I am probably trying to yield it the wrong way. I know most of you will not be familiar with Roblox, but the Lua syntax is the same.
Can someone please give me an example of how I would end a looping coroutine?
currentPlayers = {}
roundTime = 60
local lookForWinners = coroutine.create(function()
while coroutine.running do
wait(1)
for i, v in pairs(currentPlayers) do
if v.Character.Humanoid.Health <= 0 then
table.remove(currentPlayers, v)
end
end
end
end)
while wait() do
repeat display("Two or more players need to be in the game.", 1) until #_G.plrs > 1 --Ignore, just checks if two+ players are in game.
display("Picking a map...", 3) pickMap()
teleport(0, 500, 0)
coroutine.resume(lookForWinners)
wait(roundTime)
print("Round over")
coroutine.yield(lookForWinners)
end
Lua is a single-threaded language. Coroutines do not cause functions to execute in parallel.
Coroutines are effectively just a way to make a function that can pause its own execution (using coroutine.yield), that can be resumed from outside (using coroutine.resume). There is no "coroutine.running": there's only one line "running" at any given time.
If Roblox were meant for you to use wait() to jump out of the Lua thread, you would write this as a series of loops that check their condition and then call wait():
local currentPlayers={}
local roundTime = 60
while #_G.plrs > 1 do
display("Two or more players need to be in the game.", 1)
wait()
end
display("Picking a map...", 3) pickMap()
teleport(0, 500, 0)
for i=0, roundTime do
for i, v in pairs(currentPlayers) do
if v.Character.Humanoid.Health <= 0 then
table.remove(currentPlayers, v)
end
end
wait(1)
end
print("Round over")
However, this is bad code. (Whenever you write code, let loops with a "wait" function in them serve to indicate that something is being done incorrectly.) You should be using Roblox's Events to handle your game's logic.
Check to see if the game should start only when the number of players changes.
"Look For Winners" only when a Humanoid's health changes (the HealthChanged event).
Run the timer on some kind of timer or interval (don't forget that you'll probably want to end your game early once somebody has won).
Events have many, many advantages over a busy loop; the most visible one will be that your checks occur when the thing they're checking for happens, and not later.
I suggest you follow Stuart's advice to use events; this is mostly to provide additional information on what coroutines are to help you use them correctly.
Think of coroutines as functions that may return values, but with a twist: while a "normal" function completes when it executes return, when you yield from a coroutine, it saves its state, so that resume can then continue from the point where you yielded as if nothing happened. Note that you only yield from a coroutine and only to the point where the resume of that coroutine was done (this is no different from calling a function and returning from it; the control returns to the point where you called the function).
In addition to that, resume and yield calls allow you to pass values to the coroutine and return (intermediate) values from the coroutine. See this SO answer for an example of how this can be used.
One can still return from a coroutine and it's no different from returning from a function, which completes its execution. If you check the status of the coroutine at that time (coroutine.status), it should be "dead".
So, to answer your question how you can end a looping coroutine: you can return from it, you can yield() from it (and never resume it again), or you can call error(), which you can then catch and check for in the result of the resume call. Having said that, I agree with Stuart that it may be the wrong way to solve your problem.