Jump to content
  • Sky
  • Blueberry
  • Slate
  • Blackcurrant
  • Watermelon
  • Strawberry
  • Orange
  • Banana
  • Apple
  • Emerald
  • Chocolate
  • Charcoal
Gangsir

Coroutines! A Tutorial by Gangsir.

Recommended Posts

Hey, it's me Gangsir. I figured I'd try my hand at writing a tutorial on one of the coolest things (in my opinion) in lua. They say that the best way to learn and understand something is to teach it. This is what I aim to do with this hopefully easily accessible tutorial.

 

 

What?

 

Let me start off by explaining what a coroutine is, for those that don't know. A coroutine is basically a background function. It allows functions to be run without stopping the main thread. By rapidly stepping through background functions, we can achieve multi-tasking. Think of it like sending out your friend to buy a steak while you set the table. Instead of having you do everything, the final result can be achieved faster.

 

Lua.org defines a coroutine as follows:

 

 

A coroutine is similar to a thread (in the sense of multithreading): a line of execution, with its own stack, its own local variables, and its own instruction pointer; but sharing global variables and mostly anything else with other coroutines. The main difference between threads and coroutines is that, conceptually (or literally, in a multiprocessor machine), a program with threads runs several threads concurrently. Coroutines, on the other hand, are collaborative: A program with coroutines is, at any given time, running only one of its coroutines and this running coroutine only suspends its execution when it explicitly requests to be suspended.

 

As OC implements them, coroutines work like actual lua coroutines, as in that only one may run at once. As far as I’m aware, OpenOS does not support true multitasking, by having several coroutines run at literally the same time. (I.e. Threads) By quickly stepping through them, however, something close can be achieved. Whenever I refer to "threads" in this tutorial, I mean official lua's coroutines.

 

How?

 

Coroutines have three states. Coroutines can be running, suspended or dead. When coroutines are initially created, they start off in suspended mode. This means that they are paused and waiting for a resume. When in the running state, they are of course, running. And finally, when dead, they have returned.

 

To change between states, there are a few functions you can use that come with the coroutine library that is standard to lua. These are coroutine.create(), coroutine.yield(), coroutine.resume(), and return. Status of a coroutine can be checked with coroutine.status(). First of all, you have coroutine.create(). When coroutine.create() is called, you pass it a function. This is usually done via a nameless function, although you can use an existing named function. For example:

co = coroutine.create(function ()
      print("Hi")
     end)
    
--or, use an existing named function:

local function hello()
 print("Hello!")
end

co = coroutine.create(hello)

This loads up a new coroutine and assigns it to the variable co. If you try to print co, you'll get something like:

thread: 0x8071d98

The number on the right will differ. Basically, this means that co is a thread. (A coroutine! It works!) Then, when you want to run the coroutine, call coroutine.resume(co). This will for our example, print "Hi", then kill and return the coroutine. (If no return inside the function is provided, lua assumes a return with no values.) If you then check the status of the co coroutine, you'll get "dead". Example:

--">>" means what's printed to console

coroutine.resume(co)
>>Hi
print(coroutine.status(co))
>>dead

--if you then try to resume a dead coroutine, you'll get an error. No coroutine necromancy!
coroutine.resume(co)
>>(error, cannot resume dead coroutine, yada yada stack trace)

And finally, where all the magic happens. coroutine.yield(). Call it inside of a coroutine's function, and it will pause(suspend) the coroutine, and return control to the main thread. The main thread can then resume another coroutine, and so on and so forth. Caution, and this is very important, because of how OC handles things, if a coroutine does not yield after a certain amount of time (usually a few seconds), OC will forcefully kill the program it came from, crashing your code. Make sure that you yield coroutines on a common basis. This is to prevent large amounts of coroutines taking up processing power on the real-world server. Example of code that would make OC mad at you:

cor = coroutine.create(function()
         while true do --tryna lag dis server 
         end
      end)

coroutine.resume(cor)

--Code would then crash after a few seconds, with a too long without yielding error. Bad coder. Bad!

When you call coroutine.yield(), you can specify arguments that will be returned when resume is called on the coroutine. This allows for coroutines to keep a level of communication with the main thread open at all times instead of waiting for the coroutine to return, if ever. coroutine.resume() will return nil or any arguments specified in .yield(). For example:

function foo()
  print("foo", 1)
  coroutine.yield()
  print("fooAfterYield", 2)
end

coro = coroutine.create(foo)

coroutine.resume(coro)
>>foo   1 

--notice that it prints the foo and 1, which is right before the yield!
--Then, if we resume it again,

coroutine.resume(coro)
>>fooAfterYield    2

--Notice how the print changed to the one after the yield?

Why?

 

Of course, if you think like me, you likely still fail to see how this is better than just directly calling the function.

 

Say you have a program that needs to do many things at once. You can use coroutines and a dispatcher function to step through them. This allows for multitasking, by placing each function into it's own thread. The yielding function allows for control to return to the main thread when the coroutine has a good stopping point.

 

It's kind of like writing an essay. You have a page written, and it's concise and meets the requirements as it stands. You can yield and put the essay back into your backpack, or keep running and writing, or return and turn the essay into the professor. You can't mess with it once you turn it in, so if you want to keep making edits, it's better to yield and put it back inside your backpack.

 

Keep in mind that while it's in it's own thread, if a thread has a fatal crash, the whole script will likely crash. Make sure to do error handling to ensure the whole tree isn't lost if a leaf falls off.

 

 

Further questions?

 

Let me know below in the replies. Have a correction? By all means, tell me. I'm trying to fully understand coroutines as well. Like my tutorial? Give me a like and show your programmer buddies.

 

Fun Fact: The word "Coroutine" or some variant has been used 63 times in this post, as of 10/2/15.

Link to post
Share on other sites

(If no return inside the function is provided, lua assumes a nil return)

Totally unimportant nitpick, if there is no explicit return, the default isn't a return nil, it is return without any values. In most cases there is little difference between having nils at the end of an argument list and not having them, but there is a difference. This is easiest to see in the interactive prompt:

> return nil
nil
> return
> return nil, nil, nil
nil     nil     nil
> return "hi", nil, 42, nil, nil
hi      nil     42      nil     nil

Also:

> select("#", 42, 24)
2
> select("#", nil, nil)
2

(Yes, this is totally irrelevant to the topic of the tutorial. ;))

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use and Privacy Policy.