Thursday, December 3, 2015

c++ - How does Lua work as a scripting language in games?


I'm a little hazy on what exactly Lua is and how a game that is programmed in C++ would use it. I'm asking primarily about how it is compiled and run.


For instance when you use a program written in C++ which uses Lua scripts: does the code in Lua just call to functions in the main program written in C++ and act as a uncompiled class waiting to be compiled and added to the memory heap of the C++ program?


Or does it act like a bash script in Linux where it just executes programs completely separate from the main program?



Answer



Scripting is a programming abstraction in which you (conceptually) have a program (the script) running inside another program (the host). In most cases, the language in which you write the script is different from the language in which the host is written, but any program-inside-a-program abstraction could be considered scripting.


Conceptually, the common steps to enable scripting are the following (I will be using pseudo-c for the host and pseudo-lua for the script. These are not exact steps, but more like the general flow in which you enable scripting)





  1. Create a virtual machine in the host program:


    VM m_vm = createVM();


  2. Create a function and expose it to the VM:


    void scriptPrintMessage(VM vm)
    {
    const char* message = getParameter(vm, 0); // first parameter
    printf(message);

    }

    //...

    createSymbol(m_vm, "print", scriptPrintMessage);

    Notice that the name in which we exposed the function (print) does not have to match the internal name of the function itself (scriptPrintMessage)




  3. Run some script code that uses the function:



    const char* scriptCode = "print(\"Hello world!\")"; // Could also be loaded from a file though
    doText(m_vm, scriptCode);


That's all there is to it. The program then flows in the following manner:




  1. You call doText(). Control is then transferred to the virtual machine, which will execute the text inside scriptCode.





  2. The script code finds a previously exported symbol print. It will then transfer control to the function scriptPrintMessage().




  3. When scriptPrintMessage() finishes, control will transfer back to the virtual machine.




  4. When all of the text in scriptCode has been executed, doText() will finish, and control will be transferred back to your program on the line after doText().




So in general, all you're doing is running a program inside another program. Theoretically speaking, there is nothing you can do with scripts that you can't do without them, but this abstraction allows you to do some interesting things really easily. Some of them are:





  • Separation of concerns: it is a common pattern to write a game engine in C/C++ and then the actual game in a script language like lua. Done right, the game code can be developed completely independently from the engine itself.




  • Flexibility: scripting languages are commonly interpreted, and as such, a change in a script will not necessarily require a rebuild of the entire project. Done right, you can even change a script and see the results without even a program restart!




  • Stability and security: since the script is running inside a virtual machine, if done right, a buggy script won't crash the host program. This is specially important when you allow your users to write their own scripts to your game. Do remember that you can create as many independent virtual machines as you like! (I once created an MMO server in which each match ran on a separate lua virtual machine)





  • Language features: when using scripting languages, based on your choice for host and script languages, you can make use of the best features each language has to offer. In particular, lua's coroutines are a very interesting feature that is very difficult to implement in C or C++




Scripts are not perfect though. There are some common disadvantages to using scripting:




  • Debugging becomes very difficult: usually, the debuggers included in common IDEs are not designed to debug the code inside scripts. Because of this, console trace debugging is much more common than I'd like.


    Some scripting languages like lua have debugging facilities which can be taken advantage of in some IDEs like Eclipse. Doing this is very difficult, and I've honestly never seen script debugging working as well as native debugging.


    By the way, the extreme lack of interest the Unity developers have in this matter is my main criticism of their game engine, and the main reason I don't use it anymore, but I digress.





  • IDE integration: It is unlikely that your IDE will know which functions are being exported from your program, and as such, features such as IntelliSense and the like are unlikely to work with your scripts.




  • Performance: Being commonly interpreted programs, and meant for an abstract virtual machine whose architecture may be different from the real hardware, scripts are commonly slower to execute than native code. Some VMs like luaJIT and V8 do a really good job though. This may be noticeable if you do some very heavy usage of scripts.


    Usually, context changes (host-to-script and script-to-host) are very expensive, so you may want to minimize them if you're having performance problems.




How you use your scripts is up to you. I've seen scripts used for things as simple as loading settings, to as complex as making entire games in the scripting language with a very thin game engine. I once even saw a very exotic game engine that mixed lua and JavaScript (via V8) scripting.



Scripting is just a tool. How you use it to make awesome games is completely up to you.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...