Archived

This topic is now archived and is closed to further replies.

Sphet

Lua and lua_State - concurrent scripts - How To?

Recommended Posts

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.

Share this post


Link to post
Share on other sites
Sorry to be a pain, but I''m bumping this one in the hopes that someone out there can help me.

- S

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
It might be helpful to think about how a webbrowser''s javascript 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]

Share this post


Link to post
Share on other sites
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();
end
end


-- 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);
end

end);


-- 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);
end

end);



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]

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
The way I like to handle things is this:

First of all, all of my levels are randomly generated by way of scripts, which can access primitive level construction functions defined in engine code.

I create a class I call Context, which defines the current state going into a script, and provides such members as CurrentObject, which is a reference to the object calling the script. Objects of the same type can share ScriptObjects, which hold the script code, but before they call the script they set the CurrentObject member of Context so that once the script starts executing, it has a reference to the calling object and can use that calling object to keep track of any object-local state that should be remembered between calls.

So, your lamp objects could all have a reference to a single ScriptObject which implements a script turning on or turning off the lamp. If your event loop pumps the event ON_HOUR as Kylotan suggested, then each time an ON_HOUR event is passed to a lamp, the lamp sets Context.CurrentObject to itself then calls it''s OnHour ScriptObject. That script could then obtain the reference to the current object from the context, perform any necessary manipulations, then exit.

Each ScriptObject can be as simple as a std::string holding actual code, or a buffer holding compiled code... whatever you want.

For instance, here is a simple example. In my game, I have a type of object called UseableObject, which has a pointer to an OnUse script that is executed when the right mouse button is clicked on the item in inventory. Each time OnUseEvent() is called, the item does a couple things:

Context.SetCurrentUseableObject(this);
OnUse.ExecuteScript();
Context.ClearCurrentUseableObject();


OnUse is a ScriptObject that contains merely an std::string (for now) which holds a snippet of code. In the example of a simple Potion of Healing, something like this might suffice:

"local Item=Context:GetCurrentUseableItem() Player:HealLife(500) Item:DestroySelf()"

The global Lua state has a global reference to Player (as I got tired of extracting it from context). In this example, the script retrieves the calling object''s reference via GetCurrentUseableItem(), heals the player, then destroys the calling object and exits.


Of course, I am sort of locked into using Lua 4.0, due to an unbelievable amount of work I have no desire to re-write using Lua 5, so this approach may not be what you are looking for.


Josh
vertexnormal AT linuxmail DOT org


Check out Golem: Lands of Shadow, an isometrically rendered hack-and-slash inspired equally by Nethack and Diablo.

Share this post


Link to post
Share on other sites