Lua and lua_State - concurrent scripts - How To?

Started by
9 comments, last by Sphet 20 years, 6 months ago
Hello, I''m starting to investigate the use of lua in a game engine and have some questions about how people are using it. I want to use it for level scripting and entity scripting. By this I mean I want to use multiple scripts to define the behaviour of the level. One script would be the ''level script'' that defines things like trigger events, object placement, sound effect placement, and music selection. Each object that is placed would then be able to have its own script added, if needed. For example, say I have a lamp post and I want to add a script that periodically checks what time it is in the game world, and turns on when it''s after 6PM and turns off when it is 6AM. I''m looked at lua and I don''t really understand how I manage this. With Lua 5.0 I understand that there are threads now, but these seem to work by creating a new lua_State based on an existing lua_State that holds the script file. Do I need a seperate lua_State for each of my entities? If so, how do I go about yielding from each lua_State, since it seems that lua_yield() and lua_resume() only apply to threads, not the first lua_State ''core thread''. What do other people do? I''ve searched around a fair bit and have found some discussion about using ''polled event functions'', functions like doThink() and doUpdate() that are called once a frame. Of course these would work, but I would rather have a script that I can write: while ( alive ) do if ( someonenear ) then say("hello") else Sleep(1000) end end Does this make sense? Any thoughts on this topic would be greatly appreciated as I am at an impass and am unsure as to how to proceed. I don''t really want to have a single script per level since it makes sharing ''behaviours'' (like the lamppost) difficult across levels. Thanks for your time.
Advertisement
Sorry to be a pain, but I''m bumping this one in the hopes that someone out there can help me.

- S
You are relying way too much on the Force Luke.
Use the actors'' positions to tell where they are and call whatever scripts are necessary based on that.
Also, if your script is too large(it slows down the game) there are two approaches.
1) Create a thread on the machine and run all your scripts in that thread.
2) Use the thread to initiate actions rather than call them. For instance, instead of saying ''hello'', suppose that a rather long message must be played. You would use the script to add a sound to the currently-playing-sounds list and/or you would mark a flag on the object that tells it to start its animation(or perhaps tell the actor to add the sound). To tie this in with earlier, by going through that currently-playing-sounds list and checking against position, you can stop the sound or fade it if the player walks away.
Roland,

Thank you for your input.

Unfortunately, running my scrips in a seperate thread isn''t possible since the target platform doesn''t support multiple threads, at least not in the implementation we''re using. Additionally, I don''t want to have to worry about read/write lock access on shared memory objects between the two threads.

Your suggestion is well taken, but it''s of the style of ''polled event functions''; functions that exit immediately. If I do that then I have to maintain some state information within the entity instead of being able to manage it within the lua_State stack.

What about more sophisticated examples where I have dozens of entities all running seperate scripts? I''ve done some more tests and have found how to load different script-chunks into different lua threads. With this I can use lua_yield() and lua_resume, but I can''t seem to sort out how to call specific functions within the script-chunks.

Have you got any more points, or any more insite? Has anyone else? I''ve seen lots of discussion about lua in a very basic sense but never a more complex implementation.

What of #2?

Like if you want it to wait a certain time before it repeats the message, you can have another variable hold what time it last spoke the message, then, if the script gets called again you check the current time to the last time and see if it should start again.

Here''s something which is kinda bugging me, what do you mean by a ''polled event''? Polling and using events are complete opposites, they''re two entirely different architectures.

How to do level script: note that this is not something to be run every frame, this is like an initialization, a set up, you run it once and then keep track of the objects that have been created. Do you think quake loads the map every frame? My point is that it is different than other scripts.

Light On/Light Off: okay, first off, the lamp post should have a variable to determine whether its on or off, if the time is appropriate, change the state, and when you run across it during the update, display it dark or lit according to the state it is in(like turning on a light in OpenGL or switching out the tiles for ones that give the appearance of having a light shown on them).

Determining whether its on or off: ah, the fun part, and a good example of the difference between polling and event-driven design. You could run that script every so often, but it has to be very often if you want it to turn on at any time that''s actually near 6 o''clock. Okay, for what I''m about to say the previous is actually a fine use of polling, but you should get the point, to run polling effectively, every script must be run at pretty much every frame; it depends on the game of course but the point is why compute something if you don''t need to?.

Callbacks: The only way I can think of to make the light do as you say is to make a list of data structures, with each structure containing a time and the event(a function) that should take place when said time occurs. You check the list every frame and do compares, there probably won''t be so many so as to slow you down.

Timers: Another way is to create a timer. The data structure is the same, but this time you subtract the time since last update(it''s a countdown) and then compare. To create the timer you would figure out how much time is left until 6 o''clock from the point of creation of the timer.

I think SDL can do timers and callbacks, but that means you need to be sure the game-time and the computer''s time are synchronized(like in Animal Crossing on the Nintendo Gamecube, where if you play at night, it really will be night-I think, I haven''t played it). Even then, I don''t know if SDL somehow uses the system or its own internal callbacks and timers.

Now, perhaps you''re not liking my ideas too much because the entire game is scripted? Everything is done in a script, therefore things must be done in parallel?; you do not use an update-world function? In that case, you will have to recreate what many OS''s already do, a multi-tasking system. You cannot truly multitask with one processor.

As to how to make a script multi-tasking system:
1) Litigous script-making, apply a yield-continue-resume-whatever every X lines. Better yet, write your scripts, then run them through a program to add the breaks.
2) Monitoring time via script is probably way too slow, so I would change lua itself to monitor how many opcodes are processed. You may also want to add a priority system. Keeping each thread in a different state may make it easier, though I don''t know how hard sharing information across states will be.
Roland,

Thank you again for your reply.

I suppose I was thinking of a more generic sollution using lua 5.0''s yield-resume functionality. I''ll have to spend some more time investigating what exactly we need to do with our game system. Setting up callbacks from lua based on C/C++ events seems like a pretty good sollution instead of running a lot of scripts every frame.

Thanks for your input.
You''re welcome. So very many people know more than me that it''s nice to be of help every once in a while.

Lua rocks! (I use it for my projects as well.)
It might be helpful to think about how a webbrowser''s &#106avascript events work. You have events like OnMouseOver, OnMouseDown, etc. Your engine might have OnHour which is called every hour in the game. Your lamp post script then only gets called 24 times a day instead of once per frame.

Alternatively you could implement some sort of SleepUntil function where you pass it a game-time parameter. That calls lua_yield() and tells your engine to resume the coroutine at the specified point. The pseudo-code would end up looking like this:

if (time < 600 or time > 1800)    LightOn()else    LightOff()while (1)    SleepUntil(600)    LightOff()    SleepUntil(1800)    LightOn()end while 


All you have to do then is set up some sort of callback system that maps ''wake-up times'' to lua co-routines to resume. A priority queue is a good structure for this as you can avoid having to check all the pending events each frame.

[ MSVC Fixes | STL Docs | SDL | Game AI | Sockets | C++ Faq Lite | Boost
Asking Questions | Organising code files | My stuff | Tiny XML | STLPort]
Here's an example I made when I started to explore Lua, in case it helps:

-- Persistent test script-- This function is called from the application to resume the scripts wherever they left off.-- The application expects a ResumeScripts() function to be present.----------------------------------------------------------------------------------------------function ResumeScripts()   coroutine.resume(MissionScript1);   coroutine.resume(MissionScript2);end-- This yielding function waits the specified amount of time.----------------------------------------------------------------------------------------------function Wait (Duration)   local WaitStartTime = GetTime();   while 1 do      if GetTime() > WaitStartTime + Duration then break end      coroutine.yield();   endend-- Create the coroutine functions.  These function must participate in cooperative multithreading-- by calling coroutine.yield() at appropriate points in the code, such as within loops that-- could incurr a delay (unless a function called within the loop does it).  Functions called-- from these coroutine functions should do the same if necessary.-- This coroutine calls API functions to spawn actors and command some attacks.----------------------------------------------------------------------------------------------MissionScript1 = coroutine.create(function ()   while 1 do      DisplayText("MissionScript1: Starting the attack sequence.");      Wait(1);      local Victim = SpawnActor("iTransport", 680, 40, 300);      Wait(2);      local Attacker = SpawnActor("fMegatank", 340, 20, 200);      Wait(5);      Goto(Victim, 400, 40, 500);      Wait(3);      Attack(Attacker, Victim);      Wait(7);   endend);-- This coroutine just loops every 20 seconds.----------------------------------------------------------------------------------------------MissionScript2 = coroutine.create(function ()   while 1 do      DisplayText("MissionScript2: Just looping every 20 seconds, and not much else.");      Wait(20);   endend);  


Functions GetTime, Goto, SpawnActor, DisplayText etc. are API functions which bind to a C counterpart. My C++ game engine must call the Lua ResumeScripts function at a regular intervals to drive this script.

I made an MFC framework application testbed to implement the Lua script, although I used an older beta of Lua 5. It traces the Lua script calls to the C functions as output to a text editor window. It's fairly basic. E-mail me if you want the source code.

You could do some #include stuff (or the Lua equivalent) if you want the script contents to be in separate files. What is the Lua equivalent for #include anyway? I never got that far.

Regarding a script thread per entity, I think each one would need its own Lua state, but I don't think that's a problem as long as your game engine calls a "ResumeScripts" type function for each entity's state at regular intervals.

My script requires passing a handle, pointer or whatever back to the C++ so it can reference the object. I'm not sure if there is a more elegant approach.

Value of good ideas: 10 cents per dozen.
Implementation of the good ideas: Priceless.
Proxima Rebellion - A 3D action sim with a hint of strategy

[edited by - BS-er on October 17, 2003 6:06:32 PM]
Value of good ideas: 10 cents per dozen.Implementation of the good ideas: Priceless.Machines, Anarchy and Destruction - A 3D action sim with a hint of strategy
BS-er, thanks for your reply. That seems like a good example of usage. I''ll have to think about what you''re doing here.


When I am doing right now is creating a base lua_State. With that state I open the libs, register my C functions and then sit idle. When the game creates a new object, I create a new lua thrad with lua_newthread(), passing in the base lua_State. I then call lua_dofile() on the returned thread. The problem is that, when I invoke a function within the loaded thread using lua_getglobal(threadL, "Init"); the first time, and call lua_resume, everything is good. If I then create another thread which uses a different script file with it''s own Init() function, it seems to clopped the first script''s Init() function. How do I go about doing this, or is there a way I can make all my functions local and call them specifically? Help!

This topic is closed to new replies.

Advertisement