Question about coroutines used in asset Emthree (SOLVED)

Hi,

I’m a complete newbie and was wondering if anybody could help me understand this tutorial Emthree Thank you.

In particular, I don’t understand what is happening here - how exactly the coroutines are running?

What is going on with the function within fn and why do we need it?

1 Like

Lua does not support threads (ie several chunks of code running at the same time). All Lua code is run from a single thread. A coroutine can be used to run code and it allows you to run several chunks of code almost in parallel. It is still true that you cannot have multiple chunks of code running at the same time, but the code running in a coroutine can yield control and let some other piece of code run (either code on the main thread or another coroutine). A coroutine that has yielded can be resumed and the execution of code will continue at the point where it was yielded.

This is super useful because it allows you to write code that looks synchronous but is in fact asynchronous. You can for instance write code that makes an HTTP request, yields while waiting for the result and then resumes when the result is received. You can also write code that plays an animation and waits until the animation has finished before it proceeds.

This is used very frequently in Emthree. The stabilize function will run code in a coroutine until the game board is stable. Each attempt to stabilize the game board goes through the following steps:

  • Step 1. Finding and removing matches. This is asynchronous (there can be an animation playing when a block is removed). The coroutine will wait until it proceeds to Step 2.
  • Step 2. Collapse. This will let blocks fall down into empty spaces that opened up in Step 1. Again, this is asynchronous. The blocks will fall and the code will wait until it is done.
  • Step 3. Spawn new blocks. New blocks may spawn in places where there used to be blocks (that have fallen down). Again, asynchronous.
  • Step 4. Slide. Blocks may slide down diagonally. Asynchronously.
  • Step 5. If nothing spawned, collapsed or slid down then we’re done. Otherwise start from Step 1 again.

The code to run the above sequence of steps would not look as neat and sequential as in the screenshot you shared if it weren’t for the coroutines that temporarily halted execution of code while waiting for a step to execute.

In Emthree I decided to wrap the coroutine state handling in the async() function. The function takes a function and arguments as input parameters, runs these inside a coroutine and waits for a callback function to be invoked when the function is done.

3 Likes

Thank you so much for your prompt reply. :grinning:

I’ve got a few more questions, if that’s okay?

1)What does the ‘done’ parameter do?

  1. If I understanding this right, all the async functions in the stabilizer function happen one after the other. So if one coroutine gets stuck in the yield phase, then the other async functions won’t execute either. Am I getting this right?
1 Like

It’s a callback function that when called will resume the coroutine (see the async function). I designed the functions in Emthree to accept a “done” function to involve when they are … done.

Correct

Thank you so much for taking the time to explain all this stuff. Really appreciated this. I think I’m slowly starting to figure this tutorial.

But I do have another question. Why do you change variable ‘state’ to “DONE” in the async function? As far as I can see, nothing happens when state is “DONE”.

Also, what does the variable “did_spawn2” do in stabilizer function?

As I wrote in the comment it is not really needed. Still I think it’s good to indicate the new status anyway. For instance when debugging.

Excellent question! It looks like a bug. I’ll take a look tomorrow.

Sorry but I’ve another question about variable ‘state’. I promise this is the last one. :slight_smile:

What’s the point of this following piece of code in function fn when the variable ‘state’ was given the value of “RUNNING”, just before fn is executed. I can’t see where the value of state can become “YIELDED”, so the condition state==“YIELDED” is ever fulfilled.

Yeah, that was left from some experiment or something. Since it’s nil it has no impact on the code though.

I need to deal with two different cases:

  1. The code running in the coroutine is asynchronous and yields while waiting for something to complete.
  2. The code running in the coroutine is synchronous and executes from start to finish without yielding.

The typical coroutine scenario:

local co = coroutine.running()
do_something("some", "arguments", function()
	-- this callback function will be called when the
	-- async operation is done
	-- the coroutine will be resumed from its yielded state
	-- at this point
	coroutine.resume(co)
end)
-- yield until the async function is done
coroutine.yield()
-- the code will continue to run when the coroutine has been
-- resumed
print("done!")

But what if the code you are running actually completes and invokes the callback immediately within the same block of execution? The coroutine will not have had time to yield and the call to coroutine.resume() will fail. Both of these cases are handled by the async() function.

2 Likes

Phew, I think I finally understand how coroutines work.

Thank you so much for taking all this time to answer all my questions and explain the tutorial in detail. I believe, with your help, I finally understand how all this works.

1 Like

Happy to help!

1 Like

Where is the documentation for Defold’s async function?

When I search the manuals or API reference, I got the following results:

Results

manuals/collection-factory - asynchronous, asynchronously

manuals/factory - asynchronous, asynchronously

manuals/design - asynchronous

manuals/http-requests - asynchronous

manuals/resource - asynchronously

manuals/message-passing - asynchronous

manuals/collection-proxy - async_load

ref/stable/physics - raycast_async, physics.raycast_async

ref/stable/collectionproxy - async_load

It’s not a Defold API. It’s a wrapper function I created to be used with asynchronous functions.

1 Like

Ah, here’s the magic I was looking for:

setmetatable(M, {
__call = function(t, …)
return M.async(…)
end
})

thanks!

1 Like