Gangsir 14 Posted October 3, 2015 Share Posted October 3, 2015 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. Quote Link to post Share on other sites
dgelessus 26 Posted October 4, 2015 Share Posted October 4, 2015 (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. ) Quote Link to post Share on other sites
Gangsir 14 Posted October 4, 2015 Author Share Posted October 4, 2015 (Yes, this is totally irrelevant to the topic of the tutorial. ) Nitpicks are fine! Accuracy is important. Fixed. Quote Link to post Share on other sites