Sign in to follow this  
TheFallenKing

New Lua user here...with lots of questions :)

Recommended Posts

I posted this over on www.lua.org, but their site doesn't seem too....active as of late. So I'm going to cross-site post in hopes of getting a response: I've been working on developing a game and I've been trying to integrate Lua into the project and have gotten overwhelmed in the design phase (not good, I know, but scripting is 100% new to me). I've looked around the net and couldn't really find anything that helped clear the confusion, so I figured I'd try here. Ok, basically I'm trying to use scripting to control as much as possible in my game to make it completely customizable....initialisation, controls, game states, AI (especailly the AI), everything. I know this is probably my problem, too large of a scope, but that's one of the things I'm trying to determine. Just to give you an idea of the data flow I've envisioned: the game will start up and use an initialisation script to setup the window and other global parameters. It will enter it's intro state (the default first state). The initialisation script will contain a variable with the name of a script file that will be used by the intro state (it will have a variable with the name of a lua script for each of the possible game states, so that each game state is controlled by a separate .lua file) During the play state, for example, the script play.lua could say: if(key_down(p)) changeState(pauseState) So when each game state starts up it grabs the name of it's script file from the initialisation script. Now the tricky part, each Agent in my game will be controlled via a script as well, when I create a RobotAgent, for example, I'll pass the name of the .lua file that specifies how that Robot should behave...and here I come to my actual questions. 1) From what I've described, there's going to be a ton of scripts running at one time...if it is even feasible to run multiple lua scripts at once, is this an efficient design approach to take? 2) I've read that you should only need/want one lua_State* variable to control your scripts...if I do this, is it possible to run multiple scripts from the same lua_State* or do I need to read up on coroutines? One benefit I can see from one lua_State is I'd only need to register with luabind once, but that leads into a whole new set of questions I have involving luabind...I'll hold off on those for now Smile 3) Say I create a RobotAgent and need to have several instances of this running around, each one is controlled by a script in Robot.lua. I'd need separate lua_States for each instance to guarantee that the different robots didn't corrupt the global variables in the script for other robots...but this would seem to contradict what I've read for (2)? If it isn't completely obvious yet, I feel a bit like a deer in the headlights of a big 18-wheeler right now Smile I'd greatly appreciate any help or words of advice anyone could offer. Also, if you know of any tutorials/articles on Lua out there that would address these issues, please post it here. Thank you guys so much!!!

Share this post


Link to post
Share on other sites
Be careful you don't try to let lua do everything. Your performance will suffer greatly if you do this.

I'm using game monkey script myself, but the same ideas apply to most scripting languages. GM scripts has these fake threads(co-routines in lua) which allow any number of scripts to run together, and allows the script to handle yielding, blocking, sleeping, etc.

I use the scripting language in my FPS bot. It allows pretty much complete control over the bots, but still keeps a majority of the functionality in the C++ side. I take advantage of the threading support in GM script to give each bot their own 'thread', that can control the bot. For a full game each game object could in theory be running its own thread of logic. GM script comes with several console fully script powered games that do this.

Ideally for performance reasons your script execution would be limited to events, so if no events are firing, no script is being executed. It wouldn't be a good idea to execute scripts during the game loop(every frame).

Share this post


Link to post
Share on other sites
1. It's probably not all that efficient, no. The most efficient use of scripting languages is when you can use them as event-based callback handlers. Slightly slower than that might be to write the main program in your scripting language and delegate performance critical code to C/C++ functions. Running multiple scripts at once is likely to be slightly slower still due to all the context switching, and is probably more complicated than the alternative anyway.

2. As implied above, generally it's better not to run multiple scripts at once. There's nothing inherently wrong with doing so, but it creates as many problems as it solves and most people are not very good with concurrent programming at the best of times, never mind in an unfamiliar language that is interacting with another language.

You can run your scripts totally independently using separate lua_states, but then you have to watch out for the shared access to your C++ variables. (If there's no sharing though, there's no problem.) Or you can use coroutines to simulate threads but you still need to consider the implications for shared data, and also how to schedule resuming each coroutine as needed.

3. Personally I would consider reworking the scripts to be a set of functions that are called at given times. But if you really want long-running looping structures, then put the script into a function and create a coroutine out of each one. There's not much point having separate lua_states unless they share no common variables or functions, which sounds unlikely. It's not a question of avoiding corrupting data, it's a question of whether they need to access shared data (eg. regarding their environment).

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I've been looking into Lua for the same reasons as you lately. This is what I can tell from my limited experience with Lua:

I can't say much about having many lua_States, except that interaction beetween them would be difficult. I'd opt for a single lua_State for everything.

You can change the global table for functions (I can't remember the function to doit), so you can easily protect your globals, without having many lua_States.

Wether you should use coroutines or not depends on how your scripts are run. However you could decide on entity basis:



--- RoboAgent.lua

local f1=function (state)
--- Do robo1 stuff here
end

local f2=function (state)
--- Do some initialization
while(true)
--- Do robo2 stuff
state=coroutine.yield() --- Yield coroutine and on resume, get state
end
end

local co=coroutine.create(f2)

local f3=function (state)
coroutine.resume(co,state)
end

return f1,f3

--- Main.lua

roboscript=loadfile("RoboAgent.lua")
robo1,robo2=roboscript()

--- Main loop
while(true)
robo1()
robo2()
end



I haven't tested that, so there might be some bugs, but the idea should work.

Share this post


Link to post
Share on other sites
Also Lua is pretty fast, so there shouldn't be anything wrong performance wise to make the control code, main game loop and event handling in Lua, just as long as the rendering stuff and physics simulations and other CPU expensive stuff is not done in Lua.

Share this post


Link to post
Share on other sites
I've basically implemented (I think) what you are trying to do...

I would rein in your ideas slightly though.

For scripted objects, I define global tables objectList and objectType.

For each new type of scripted object, I add it to the objectType table with the object's type-name as the key, and for each instance I add it to the objectList table with a unique Id as the key:

e.g. largeTank object type would go into objectType["largeTank"], and an instance of it would go into objectList[0]. objectType entries are tables containing a function create(), update(), handleMotion() and handleOrders(), as
well as any other objectType-specific data I wish to use.

When I wish to create an instance of an object type, I simply call objectType["largeTank"].create(-parameters-).

objectList entries are tables with entries pointing to the associated objectType field as well as any other data I need. When I call createObject C++-side, It automatically creates the new objectList entry and adds its Id to a global list.

Every update-frame: Call every objectType.update() function, objectType.handleMotion() function, and if we have orders pending, objectType.handleOrders() function.

The key is to create a library of C++ support functions to reduce the workload on your scripts.

Share this post


Link to post
Share on other sites
I was worried about the performance/customizablility tradeoff. Outside of using the script to dynamically setup a level, game parameters, I was looking at a structure like:
In each frame
{
for_each_game_agent
{
agent.update()
}
}

Where Agent::update() would call a lua script for the agent. This script would look something like this:
function update()
evaluateAIGoal()

if(keyDown(key_left))
moveAgent(left)

if(enemyInSight())
shootEnemy();
end

And the function calls within the script would be member functions of various classes (I'm planning on using luabind). Sorry for all the pseudo code, but I'm still planning at a high level and I'm trying to get guidance/advice without asking y'all to hold my hand :)

Share this post


Link to post
Share on other sites
Ok... given that sort of layout, you're not actually running several scripts at once - you execute one, then the next, then the next, etc. This makes things a lot easier, providing you don't want to start doing long-running tasks in any of those scripts. Performance there should be perfectly adequate providing that functions such as 'evaluateAIGoal' are largely implemented in your compiled language.

Share this post


Link to post
Share on other sites
Kylotan,
I'm not sure I follow the "execute one, then the next, then the next, etc." logic. I may be missing something in the lua doc, but I don't see anythign to stop a script besides the coroutine yield.

The biggest problems I see with calling lua_dofile multiple times is
a) I'd have to have unique variable names across all of my .lua files. I could put them all in tables, but then the table names would still need to be unique.
b) More importantly, I wouldn't be able to use something like Robo.lua to control two unique instances of a Robot.

Share this post


Link to post
Share on other sites
Hello :)

I'm one of the coders working on Mistlands and we use lua to script objects (entitys) in the games, gui and materials on models. A short description of how we do it might give you some ideas :).

Each entity in the game has it own .lua file (like dwarf.lua). Each lua file gets it's own lua_State that are used to load it. It's event based were the engine calls functions in the lua file when things happen.

Each time a function in the lua state for a entity is called the ID of that entity is stored so you know what entity to act on. All dwarfs use the same lua file but when you tell the dwarf to move somewere you call MoveTo(GetCurrentEntityID(), Position); so only the correct dwarf starts moving.

Variables for entitys are stored in the entitys and not in lua global variables. To set the name of the dwarf you do SetLocalString(GetCurrentEntityID(), "Gimli");. The name is then stored in the entity and you can get it back with myname=GetLocalString(GetCurrentEntityID(), "Gimli");.




Share this post


Link to post
Share on other sites
Quote:
Original post by TheFallenKing
I'm not sure I follow the "execute one, then the next, then the next, etc." logic. I may be missing something in the lua doc, but I don't see anythign to stop a script besides the coroutine yield.


The example you gave of a 'script' was a function. When you call a function, you start at the top and come out somewhere at the end and it's over. In other words, it's executed. Now, if you're saying that you need things to be ongoing and deliberately yielded, that's a different question, but it's not what your example suggested.

As for lua_dofile, that's probably not the way you want to go about it. Instead you probably want to load individual scripts as functions and call them as necessary, or load in object definitions which other code creates when necessary. I don't even know if lua_dofile is exposed in the latest version. You probably want lua_load, often via luaL_loadfile.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Edit: wow this ended up long.

Here's an example of what one of my bot scripts look like.

When a bot is created, they optionally have a 'profile' assigned to them. This profile is basically just a reference to a script file. In my case its a game monkey script file, but it could be lua or whatever.

Among other things, the script allows a user to assign a custom goal function that the bot will use instead of its built in C++ goals. This allows a user to optionally take complete control of certain bots to script scenarios, machinima, tutorials, etc..


// Test Bot Script
goalFunc = function()
{
block(event.spawned);
this.GoTo(5.14, 392.77, 24.125);
if( block(event.goal_success, event.goal_failed) == event.goal_success )
{
this.BotCommand("say Wohoo, I made it!.");

dowhile(true)
{
closest = this.GetNearestAlly(cat.player,0);
if(closest != null)
{
info = this.GetTargetInfo(closest);
this.TurnToPosition(info.position);
} else
{
this.BotCommand("say No Target.");
}
yield();
}
}
else
{
this.BotCommand("say Sorry, couldn't make it.");
}
};
this.SetGoalFunction(goalFunc);


GM script allows the user to tell the scripting system what the value of 'this' is. When the c++ side executes this function, it sets the value of this to the bot pointer.

In this example, a function is declared, and at the bottom a function is called on the bot that sets the goal function to this script function. Part of the C++ implementation of this disables any default internal 'thinking' of the bot, creates a gmThread for the bot, and executes the function. Note that gmThreads aren't real threads like OS threads, they execute until the script yields/blocks/sleeps. GM Script has some nice built in functionality for these threads, the block(), yield(), and sleep() functions.

As you can see in the example, the script first blocks on an event.
block(event.spawned);
This stops execution of the script completely, this script will no longer execute until the contents of the value pass in to block() is signalled. With a simple hook in my event system in c++, when the bot spawns he will send the gmThread a signal(event.spawned), which will then unblock the thread and allow it to execute on the next update of the scripting system.

Ok, so as soon as the bot spawns, in this simple example a c++ bound function is called on the bot,
this.GoTo(5.14, 392.77, 24.125);

All this does it push a GoalGoTo on the bots goal queue, and the script immediately blocks again, this time defining several signals that can resume the scripts execution. It could take the bot several minutes to get somewhere, and he will do whatever it takes to get there. Part of the GoalGoTo is building a path from the bots current point to the goal location. Anything necessary to accomplish the goal will be automatically handled c++ side, based on the make-up of the goal. He may end up getting GoalNavigateElevator, GoalJumpGap, or any number of sub goals added based on the complexities of the path he is navigating. The important part is that the script is blocked at this point, and will not continue until the goal has succeeded or failed.

It's a simple example, but as soon as the bot succeeds in getting to the point, the bot will signal his goal thread to continue, say "Wohoo, I made it!." and then the script will go into a loop where he continuously checks his sensory system for any memory of the nearest ally. If there is one, he gets their target info, which is a c++ bound type that holds distance, position, velocity, orientation... and calls this.TurnToPosition(info.position); which causes the bot to turn to face the target over time based on the turn rate, spring stiffness/dampening aiming model.

Loops are potentially dangerous, so its important to notice that the yield() is in the loop, which means this loop will do 1 iteration each update and give control back to the bot dll. If the yield wasnt there the script would loop forever.

The top-down execution of this type of scripting system has many advantages.
1) It's easy to understand, and much less code than if you were to try and handle the asynchronous nature of most tasks you give a bot or game entity. Even non coders could probably create a tutorial or scripted bot relatively simply.
2) It spends very little time executing actual script code, meaning the speed penalty would be very little compared to running a script function every update that may or may not do something.

It's very easy to do most anything. The ability to stop execution of a script until the line before it completes is very useful in keeping scripts simple, small, and easy to debug. Especially dealing with AI, like this example, nothing happens instantaneously. It takes time for the bot to get somewhere, it takes time for the bot to turn to face a target. It takes time for a door to close, a treasure chest to open, an elevator to rise. All these would be insanely trivial to write a script for.

In the example above, I could replace the this.TurnToPosition(info.position);
with

if(this.TurnToPosition(info.position))
{
this.PressButton(FIRE);
}

since my TurnToPosition function returns true if the bots aim is within a small degree of error to the target, and false otherwise.

A door script could look like.

// Script for a locked door.
// When someone 'uses' this door, the script will set the activator, and
// signal the doors thread with "activated", allowing the door to do whatever
this.CloseDoor(); // we want this door to start closed.
dowhile(true)
{
block("activated");
activator = this.GetWhoeverActivatedMe();
if(activator != NULL) // paranoid error checking
{
if(activator.HasItem("bluekey"))
{
this.OpenDoor();
Sleep(this.GetDoorOpenDuration()); // stay open for however long
this.CloseDoor();
}
else
{
activator.SendMessage("The door is locked, find a key!");
}
}
}


With a very simple set of functionality on the C++ side, this would empower your designers/map makers to create incredibly diverse environments, by giving them the flexibility to do pretty much whatever they want. This script could trigger a trap, warp the character, etc. It could also be expanded easily. If the game has more actions than "activate" it could do something like this.


dowhile(true)
{
actionDone = block("activated", "inspected", "bashed");
if(actionDone == "activated")
{
// do the stuff above
}
else if(actionDone == "inspected")
{
activator.SendMessage("An old oak door. It looks pretty weak with age.");
}
else if(actionDone == "bashed")
{
if(activator.CanBash(this))
{
activator.SendMessage("You broke the doors lock!");
this.OpenDoor();
break; // the lock is busted, break out of the loop, the thread will end and the door will stay open
}
}
}


This type of thing could come in very handy in a MUD, where each action can be a command the user typed in, allowing anyone to implement custom keywords or actions at any time.

Ok anyways, apologies for the length of the post. Game Monkey script has very easy, and very well implemented support for this stuff out of the box. I'd recommend taking a look if you need this type of functionality. I'd take GM script over lua anyday, for these 'threads' the built in 'state' functionality, plus a more familiar syntax(I hate luas syntax). Alternately you could probably do something similar with lua co-routines but it wouldn't be very pretty from what I've read about them.

Share this post


Link to post
Share on other sites
sigh, I hate this forum, that last post was me.

do anon posts disregard code tags? grr it looked fine in the preview.

Can a mod please fix my code blocks?

Share this post


Link to post
Share on other sites
Anonymous posters can't use HTML. The links were completely stripped.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this