Game logic and concurrency in Lua

This topic is 3287 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hello, I was wondering how to run Lua chunks in a non-blocking fashion, as if I were assigning a "thread" (actually a coroutine) to every entity with scripted behaviour, so that they could act asynchronously. I suspect the answer lies in coroutines, however I'm a bit unclear on their proper usage. Has anybody got an interesting link or advices to share on the matter? I think this is not an uncommon issue, and the solution will probably be easier than all the complicated ideas I'm having at the moment. :) Thanks in advance!

Share on other sites
coroutine are not asynchronous, mostly because normal Lua has no concept of threads. They are a form of cooperative threading as it requires you to switch to them to use and yeild from them to allow other "threads" to run.

Share on other sites
Thank you, phantom. What are my options, then?
"coroutine.yield"ing every other line of code? It just doesn't sound right.

If anyone has an experience in scripting many different game objects they feel like sharing, I'd be very appreciative.

Share on other sites
You are correct in saying that yielding on each line would be wrong.

The biggest question is why do you want to have these things update at the same time? This is going to cause you nothing but pain, partly because Lua isn't thread safe.

Share on other sites
Quote:
 Original post by RainweaverThank you, phantom. What are my options, then? "coroutine.yield"ing every other line of code? It just doesn't sound right.

Probably the most common way is to call coroutine.yield() at points within your script that require waiting for some reason. Other instructions resolve instantly so you don't need to yield.

Share on other sites

My main concern was performance under heavy workload; however seeing as heavy is subjective, I am talking about a world populated with thousands of entities, each entity having its own behaviour.

I haven't written any code yet, I'm just considering best practices so that I know I have a properly working codebase. I'm not talking about optimization as it's something I'd rather do with actual tests.

I take it couroutine.yield should be invoked before, say, a blocking task such as moving to a point of a specified path?

I was also considering simple statements such as adding or subtracting from a health variable; while I'm sure they're absolutely fast, having thousands of such statements made me think they'd quickly stack up and get slow.

This is all based on assumptions so I'd love to hear your point of view.

Thanks again.

Share on other sites
Lua is plenty fast for most things, I plugged in some test for basic operations so you can get the idea. For something simple like basic arithimetic operations, it can do about 25 million add operations per second ( this is with LuaJit on a AMD 64 3.8Ghz single core machine. ).

local count = 1000000*10;local start = os.clock();print("testing out add operation")--note a is globala = 0;for i=0,count do	 a = a + 1;endprint("done")local countDone = count;local stop = os.clock();local time = (stop-start);local perSec = 1/time;print("test took "..time.."secs");print("can do "..(perSec*countDone).." calls per second");print("can do "..((perSec*countDone)/60).." calls per frame at 60fps");print("realstic 5% "..(((perSec*countDone)/60)*.05).." calls per frame at 60fps");

with the resutls..

test took 0.3910000026226secs
can do 25575448 calls per second
can do 426257.46875 calls per frame at 60fps
realstic 5% 21312.873046875 calls per frame at 60fps

Ofcoruse this benchmark is purely synthetic, in real world situations your performance will be constrained not by basic operations such as these but more likely the interface layer between C++/Lua, complex functions like spatial queries and complex logic of the entites themseleves.

What Lua gives you is flexible runtime scripting and execution safety (if you make sure all functions you expose to Lua are also safe) within high performance dynamic langauge.

Enjoy!

[note: I'm sure os.clock() isn't the best timer to use, but this is just a ball park figure, so take that into consideration.]

-ddn

Share on other sites
Thanks for the benchmark ddn3, very kind of you :)

I forgot to mention I'm using Lua via LuaInterface (CLR); however, I presume the figures shouldn't differ too much, and if they do, nothing to be worried about for the time being.

I have enough informations at the moment, thanks again everyone.

Share on other sites
Quote:
 Original post by RainweaverI take it couroutine.yield should be invoked before, say, a blocking task such as moving to a point of a specified path?

Usually you merge the yielding and the blocking task together.

eg. Wait(time) would pass the time parameter to the engine and then yield. The engine would then only resume the coroutine when that time has passed.

eg. MoveTo(x,y) would tell the engine to calculate a path, then yield. The engine would resume the coroutine when the character reaches the destination.

Quote:
 I was also considering simple statements such as adding or subtracting from a health variable; while I'm sure they're absolutely fast, having thousands of such statements made me think they'd quickly stack up and get slow.

If they need to be done, they need to be done, no matter how you handle your coroutines.

Generally though, most scripted behaviours are fairly short. You don't tend to find yourself running thousands of lines of script code.

Share on other sites
I think a more interesting question is whether it's worth it to run different Lua (co)routines in different physical threads. And if so, how to synchronize access to global and shared variables elegantly, efficiently and transparently.

Share on other sites
There are several Lua threading libraries which tackle the task of sharing data between true concurrent threaded Lua VMs, you can try them.

You could wrap a unique table in Lua which is actually a thread safe container for reading/writing values which are shared across threads.

Some go the message based route where it acts like a trasnacitonal database, you query/update a shared data container.

As long as your not hitting a shared variable 1000s of times per frame any one of these approaches would work, as that's how they are done currently in multi-thread programs and it seems to function well enough.

-ddn

[Edited by - ddn3 on January 19, 2009 1:30:00 PM]