开发者

What are the benefits of coroutines?

I've been learning some lua for game development. I heard about coroutines in other languages but really came up on them in lua. I just don't really understand how useful they are, I heard开发者_如何转开发 a lot of talk how it can be a way to do multi-threaded things but aren't they run in order? So what benefit would there be from normal functions that also run in order? I'm just not getting how different they are from functions except that they can pause and let another run for a second. Seems like the use case scenarios wouldn't be that huge to me.

Anyone care to shed some light as to why someone would benefit from them?

Especially insight from a game programming perspective would be nice^^


OK, think in terms of game development.

Let's say you're doing a cutscene or perhaps a tutorial. Either way, what you have are an ordered sequence of commands sent to some number of entities. An entity moves to a location, talks to a guy, then walks elsewhere. And so forth. Some commands cannot start until others have finished.

Now look back at how your game works. Every frame, it must process AI, collision tests, animation, rendering, and sound, among possibly other things. You can only think every frame. So how do you put this kind of code in, where you have to wait for some action to complete before doing the next one?

If you built a system in C++, what you would have is something that ran before the AI. It would have a sequence of commands to process. Some of those commands would be instantaneous, like "tell entity X to go here" or "spawn entity Y here." Others would have to wait, such as "tell entity Z to go here and don't process anymore commands until it has gone here." The command processor would have to be called every frame, and it would have to understand complex conditions like "entity is at location" and so forth.

In Lua, it would look like this:

local entityX = game:GetEntity("entityX");
entityX:GoToLocation(locX);
local entityY = game:SpawnEntity("entityY", locY);
local entityZ = game:GetEntity("entityZ");
entityZ:GoToLocation(locZ);
do
  coroutine.yield();
until (entityZ:isAtLocation(locZ));
return;

On the C++ size, you would resume this script once per frame until it is done. Once it returns, you know that the cutscene is over, so you can return control to the user.

Look at how simple that Lua logic is. It does exactly what it says it does. It's clear, obvious, and therefore very difficult to get wrong.

The power of coroutines is in being able to partially accomplish some task, wait for a condition to become true, then move on to the next task.


Coroutines in a game: Easy to use, Easy to screw up when used in many places.

Just be careful and not use it in many places. Don't make your Entire AI code dependent on Coroutines.

Coroutines are good for making a quick fix when a state is introduced which did not exist before.

This is exactly what java does. Sleep() and Wait() Both functions are the best ways to make it impossible to debug your game. If I were you I would completely avoid any code which has to use a Wait() function like a Coroutine does.

OpenGL API is something you should take note of. It never uses a wait() function but instead uses a clean state machine which knows exactly what state what object is at. If you use coroutines you end with up so many stateless pieces of code that it most surely will be overwhelming to debug.

Coroutines are good when you are making an application like Text Editor ..bank application .. server ..database etc (not a game). Bad when you are making a game where anything can happen at any point of time, you need to have states.

So, in my view coroutines are a bad way of programming and a excuse to write small stateless code.

But that's just me.


It's more like a religion. Some people believe in coroutines, some don't. The usecase, the implementation and the environment all together will result into a benefit or not.

Don't trust benchmarks which try to proof that coroutines on a multicore cpu are faster than a loop in a single thread: it would be a shame if it were slower! If this runs later on some hardware where all cores are always under load, it will turn out to be slower - ups...

So there is no benefit per se.

Sometimes it's convenient to use. But if you end up with tons of coroutines yielding and states that went out of scope you'll curse coroutines. But at least it isn't the coroutines framework, it's still you.


We use them on a project I am working on. The main benefit for us is that sometimes with asynchronous code, there are points where it is important that certain parts are run in order because of some dependencies. If you use coroutines, you can force one process to wait for another process to complete. They aren't the only way to do this, but they can be a lot simpler than some other methods.


I'm just not getting how different they are from functions except that they can pause and let another run for a second.

That's a pretty important property. I worked on a game engine which used them for timing. For example, we had an engine that ran at 10 ticks a second, and you could WaitTicks(x) to wait x number of ticks, and in the user layer, you could run WaitFrames(x) to wait x frames.

Even professional native concurrency libraries use the same kind of yielding behaviour.


Lots of good examples for game developers. I'll give another in the application extension space. Consider the scenario where the application has an engine that can run a users routines in Lua while doing the core functionality in C. If the user needs to wait for the engine to get to a specific state (e.g. waiting for data to be received), you either have to:

  • multi-thread the C program to run Lua in a separate thread and add in locking and synchronization methods,
  • abend the Lua routine and retry from the beginning with a state passed to the function to skip anything, least you rerun some code that should only be run once, or
  • yield the Lua routine and resume it once the state has been reached in C

The third option is the easiest for me to implement, avoiding the need to handle multi-threading on multiple platforms. It also allows the user's code to run unmodified, appearing as if the function they called took a long time.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜