Embedding Lua in C++ #1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello in this video we're going to look at how we can change the behavior of a program after we've compiled and released it and I'm going to do this by looking at using an external scripting language to augment the functionality of our original program in this case I'm going to be using Lua because I like Lua it integrates with C++ very well and it's a very quick language and it's also still tactically quite simple the practice of using scripts to augment our programs is getting more and more common particularly in the gaming world where the modification of games is actually becoming a business in its own right it's also very useful for businesses and industry to be able to modify their programs rapidly without needing to recompile the original source or get hold of the original developers so I'm going to take a very quick look at how we can get Lua and C++ talking to each other but I should point out that this video is not intended to be a Lua tutorial I believe Luard is syntactically simple enough that if you've got even the most basic programming knowledge you'll be able to look at some lower code and understand its purpose I'm going to start with an empty C++ program it's just an int main but so I don't have to answer the same question a thousand times over I'm just going to quickly show how we get hold of Lua and set it up firstly go to the Lua dot org website this is the home of all things Lua and click the download button now you can't download the entire source for Lua but I'm not going to do that instead what I suggest is you go to binary and you download the binary zand set what we don't want to do is compile all of the source if you are Linux you might want to do that but I don't and then click on the latest version of Lua so in this case it's five point three point five and go and download what you need so in my case it's windows library I'm going to download the dynamic version of Loess it's going to be loaded in as a dll and I'm going to choose the 32-bit version which is this top one here and here is the zip file I've just downloaded it contains a static library a dynamic library and an include folder with some header files in at this point I should probably mention that I'm showing you how to do things the way I do it which might not necessarily be the best way in fact when it comes to embedding external languages there are many many many ways to achieve the same thing how you come to that decision depends on the application that you're building and how you intend it to be used since Lua is so small I like to include a local copy of it in my C++ project on the right here I have my visual studio solution so I'm going to create a new folder in here and I'm going to call it Lua 5 3 5 which represents the version we've just downloaded and into that folder I'm going to copy the contents of the zip file I'm then going to take a copy of the DLL file and paste it directly into the project folder and as I've just mentioned there are a million in one ways of achieving the same results particularly on Linux you may want to install Lua to a known location and always reference that one instance of Lua Lua is fundamentally AC library so I need to tell the compiler that I'm going to be dealing with C defined functions I do that using the externally word and then I'm going to include the Lua header files that I need directly referenced to the local Lua 5 3 5 path of the visual studio project after we've included the header files we need to link to the library now in Windows and visual studio I can use a hash pragma for this will be lots of people complaining about that because not all compiler support it so I'm guarding it with win32 this approach saves me from having to alter any of the project properties directly depending on the building tool chain that you're using you may need to include additional directives to the compiler to link to this library and that's it that's all we need to start using Lua one of the nice things about Lua is it doesn't really get compiled until you need it so it always exists in a nice text form and this is perfect for modification because it allows other people to edit that text in an understandable way let's start with a very simple example to demonstrate what I mean I'm going to create a standard string called command and I'm going to define something in that command a equals 7 plus 11 the goal here is to get lower to execute this expression and return the result to do anything with Lua you need to create a Louis State now the function syntax for interfacing with Lua is a little bit old-school and worthy but actually using Lua is quite simple in intuitive and lure convention dictates that a single capital L represents a single instance of a lower virtual machine so here I have created that lower virtual machine I'm now going to tell that instance of the virtual machine to do something and it'll return an error code whether it could do it or not in the simplest possible way I'm going to tell Lua to simply execute that string with the do string command I pass in an instance of the lower virtual machine and I'm also going to pass in my string now because this is a C function standard string doesn't really apply so I need to actually get that as a character buffer using the C strip method and now we've reached the first and most important lesson when embedding any external scripting language you must validate what's happening because you've got ultimately no control of what the end user might have scripted the script could contain syntax errors it could contain malicious code it could contain code that locks up and freezes your program and there are other things too regarding the information transferred from these C++ domaine to the scripting language to make and we'll look at that in a bit more detail later so the first thing I'm going to do is make sure that that string executed successfully by checking the value that was returned in this case we can check to see if our was equal to Lua ok if it wasn't ok I'm going to interrogate the virtual machine about the nature of the problem and to do that I'm going to interrogate the Luas stack and I will go through what the Luas stack is in a minute but right now just stay with me assuming the script did execute successfully we want to get the value a from our command well in this case a exists globally within the lure virtual machine so I'm going to use the glue a get global function to give me access to the variable a and here in lies another round of validation I don't know what type a is Lua fundamentally is reasonably tight plus this is very different to C++ where everything is quite explicitly defined what it is and how it's stored in memory now I am acting the result a to be a number and so I'm going to test explicitly is it a number and again we see this strange notation and I will describe what that is in a minute knowing that a is now definitely a number I can reasonably safely convert it to a C++ float so here I've created a variable a in C++ and I've used the lure to number command to convert whatever a was into a number and I'm casting it to a float I'm now just going to output that value to the console now there's a further little bit of housekeeping to do before people start writing in and complaining whenever we've created a lure virtual machine we should also clean it up we can use the lure close function to do that and this will make people really angry I'm also going to throw in a system pause just because it makes making the video a bit easier so here we've got a simple program we take a command a equals 7 plus 11 we create a lure virtual machine and we tell it to execute that command within check did it successfully execute the command and if it did we try and extract the value a as a number so we can display it in C++ if it didn't successfully execute the command then I'm going to display any error messages that the lure virtual machine has returned so let's take a quick look well we can see that lure was successful in that it added 7 and 11 and gave us the result 18 but let's see how helpful Lua can be by adding a syntax error to our command in fact it even returns on line 1 of our command the number was malformed so what's this all about then Lua and C++ talked to each other various stack and if you remember a stack is a first in last out data structure so as data gets put in it starts filling up the box and in principle you can only take data out of the box if it was the last item put in it however the Luas stack does kind of break this rule as it allows us to index individual elements within the stacks buffer and this is quite an elegant solution to a particular problem as I've already mentioned a in Lua could be anything we've not declared a type for it a is simply some object with a value and this presents C++ with a problem because it likes everything organized and defined so I want us to conceptually envisage that each element on this stack is actually a box that contains some data and a description of how that data might be interpreted and so when we call functions like Lua is number what we are doing is asking the box to test its contents to see if it can be interpreted as a number when we called the Lua get global function and if you remember we passed it at the luer virtual machine and our variable an interesting transaction occurred into the stack we put the variable a Lua then went away and popped a off the stack and pushed onto the stack the box that it has that defines what a is and in this case a is also associated with a value 18 the Luas stack can be indexed in two ways one is explicitly starting at one oddly so one two three four five six which implies that zero is an empty stack however Lua also provides a second way to index a stack and you can index it relative to the top of the stack so the top is minus one the next one down is - 2 - 3 - 4 - 5 etc etc etc this is a convenient notation to make sure you're always working with data near the top of the stack which is likely given the functions that you've been calling no doubt this does take a bit of getting used to and we'll see it does become reasonably complicated when you start exchanging more sophisticated data structures but what is quite nice is it is always deterministic so you can always work out where you are on the stack so looking at our get global function we can see that we're effectively pushing the name of a box a on to our stack Lua then looks for a box named a and replaces it on the stack with the contents of that box we know that the -1 index is going to be the top of the stack so I can interrogate that location on the stack to see can that box be interpreted as a number well in this case it can because it contained the value 18 since it can be interpreted as a number I then ask Lua to go and directly convert its contents of that box to a number that C++ can understand when we put the syntax error into the command it didn't know how to execute the command and it placed an error message as a string on the top of the snack I wouldn't blame you for thinking well so what this all seems like a lot of effort to add 7 to 11 but Lure is actually quite a powerful and well supported language so we can use more complicated functions at within the framework so let's say I wanted to take the sine function of some value let's see what happens this time oh well we've got a syntax error attempting to index in nil value it doesn't have any representation for what math was in that command well math is in fact one of many Lua libraries but we need to tell our lure machine which libraries it can have access to because don't forget being safe when you're running people's code and you don't know what they've written is very important explicitly listing all of the libraries by hand can be quite tedious so Lua thankfully provides the function Lua open libs which opens sort of the basic set of libraries that most programs will use this is now added math to the virtual machine so let's try it again and we see this time it's quite happy to compute it and we end up with well whatever the result is the standard libraries in Lua provide all of the basic things that you might expect a programming language to provide including things such as file handling an operating system manipulation but since Lua has a robust community around it many other third-party libraries are also available that can do pretty much anything and everything you need it to do since I'm going to be checking the status of the luer virtual machine quite frequently I'm going to create a little utility function to help do that it just checks for an error message which means I can simplify my program I think it's important at this point to demonstrate that the luer virtual machine is persistent so any changes to it lasts between calls so in the first command we executed this maths expression but let's say on the second command I want to use the result from the first one so a equals a plus 100 for example I'm then going to reuse the code I've created to execute the next line so we can see the first time we got the original result and then we got the same result at plus 100 with a little bit of floating-point rounding and this implies that we can sequence instructions to the lower virtual machine which makes sense it is after all a fully functional programming language in its own right but it also means that we could construct these instruction commands as necessary in our C++ code but let's be honest doing things line by line sucks what we actually want to run is a Lewis script and then to add a new text file to my program and I'm going to save it as in this case it's going to be video example dot lua then i'm going to add that text file video example dot lua still got the text at the end of it gets rid of that I'm going to add that to my visual studio workspace now nicely Visual Studio can syntax highlight Lua so things get a bit simpler to read so I've got what is a Lua script and a Lua script is quite an important concept because here the strings are still hard-coded within the C++ program but if we now externalize them as a script other people can edit them and our program will run them let's test this by taking our two silly commands and adding them to the script we'll save that I'm going to go back to the C++ program and I'm going to delete this second half and instead of the do string command I'm going to call the do file command and instead of the command as a string what is expecting is a file named video example dot lua we no longer need the command we can get rid of that so now lua will load this file and execute all of it let's take a look well no surprises we get exactly the same results as we did before but without needing to recompile the program I can change the nature of the script and rerun and you see visual studio didn't even compile it just ran the lua script which is perfectly fine that's exactly what we want and this framework allows us to do our first simple thing with lua and that's just use a lua script as a settings file for your application so i'm going to define some values in my lua script which i'm going to import into my c++ program we can see in these settings this some common linkage they all look like they define a player so in my c++ program i'm going to create a struct which represents a player and will create a single object player we've seen already how to interrogate global values in a lua script this time instead of looking for the variable a I'm looking for the string and I'm expecting it to be a string and that's an important thing is string and instead of returning a floating point this time I'm going to convert to string so this feeds into what I was saying earlier it's very important that you know what the nature of the data you are expecting is I've modified the code here to simply display the player's name Kieran excellent now a naive approach is to duplicate this for all of the different fields so I've just modified the program to read in all of those settings obviously Squire Kieran over will level 20 and I don't think I even need to demonstrate that if I change these settings the string will change in the C++ program so using Lua is simply a configuration file for something is quite simple but it did seem a little involved there's a lot of effort to go to verges to extract the data that you're looking for now that's because of how we have structured the settings in lure itself since we've got lower available to us we may as well leverage its abilities to handle information for us so instead of defining all of the properties of a player object individually let's actually create a player object and lure being lure there's several ways to go about doing this so a simple one is using key and value pairs so let's give it the key title and give it the value what we have here starts to demonstrate the power of Lua we've effectively created a box called title and given the box contents of Squire and we've put that box in an overall box called player Lua has additional ways of reaching the same goal so let's have a look how we interrogate this global object player to get the contents and I'll just comment these out well starting at the top of our C++ code we know that player name is no longer what we're looking for what we're looking for is a global object called player now player as we defined it is no longer simple type it's actually a table containing names and values so I'm going to make sure that the global that is found is in fact a table and just to keep things clear I'm going to remove the previous code to find a particular element in a table we need to add the name of that element to the Luas stack so I'm going to use the Lua push string command to do just that and what's important to note is that the location of the table has now changed - one indicated it was at the top of the stack now at the top of the stack is the string name the table will be at minus two a call to the lure get table command don't forget it's at minus two we'll look at the top parameter of the stack find the relevant key value per pop the name off the top of the stack and replace it with the value that we're looking for so the top of the stack now contains the value of the key that we were searching for however our stack is now not arranged the way we think it might be so we need to maintain our stack by popping the top element off hmm maybe someone notes in order here after the first call to get global we know that the object player was placed at the top of our stack remember that these things on the stack are essentially boxes with a name so this box contains a table so I used the Lua is table command on the top of the stack to verify that and indeed it is a table it is an object that consists of other things those are the things are keys and values and not to confuse things too much but these values could be other tables but I'm leaving that well alone for the time being the top of our snack was denoted as minus 1 I want to search my table for a specific key value per in this case I want to use the key name to try and find the value ciarán so I need to issue the key to the lower virtual machine and I do that by pushing it onto the stack this has now changed the indices because minus 1 is always the top of our stack and minus 2 becomes the location where the table existed I then call the lower get table command being careful to make sure that I choose the right slot on the stack this command uses the value at the top of the stack in this case it's the string name it pops that off and uses it to return the appropriate value once the get table command successfully executed I then call lure to string on - one on the top of the snack to ultimately get the value that I'm interested in but now I'm left with a value at the top of my stack that I no longer want so it's important to pop that off the stack resetting the stack to how it was at the start if I choose not to pop the value off the stack I can still do things I just have to maintain the indices properly this can get quite complicated when working with large structures and structures with nested structures so I find it's always better to maintain your stack at the micro level rather than the macro level this means I can cut and paste the same code now to get the other elements of the structure I'm looking for and you may notice I can also do things out of order because I'm doing a key value search each time the odd one out is of course level even though the key is a string the value were expecting is an integer just before I run it a quick reminder of what the original script looks like and I'll click play and of course we see the result that we expect so far we've looked at very simple methods to extract information from a lure file and they can get a lot more complicated than what I've shown here again it depends on your application and how you're approaching the solution to your problem but what we really want to do with external scripting languages is get them to solve problems for us ie it would be good to be able to call functions in Lua from our C++ program so let's have a look at just that for now I'm going to comment out this code because I want to reuse it later I'm going to go back to my Lua scripts and I can leave that there I'm going to create a function I'm going to call it add stuff and the function is just going to take two variables we're just going to simply add them together and return the result my variables are going to be a and B of course a function must return something so I'm just simply going to return a plus B but I'm also going to add a little utility to help us keep track of where we're up to you don't need to do this but it may the video a little bit more visual interesting I'm going to use the print command to inform me that lure has indeed been cold so I'm going to have a little square brackets with lure in it and a note that the add stuff function has been called in fact I'm also going to put in the parameter values the two little dots basically concatenate things together the lure print command will output to standard out that's the same console that I'm using in my C++ program calling a lure function is actually quite easy but I will stress that from this point on in the video I'm not going to include all of the necessary error checking just to keep things a little bit clearer the function just like any variable is just defined as a box with something in it in this case it contains a function so it's important that we first of all get that box and put it on our Lewis stack I will test to make sure that what is in that box can be interpreted as a function if it can I'm going to push two things onto the stack let's push two numbers just directly three point five and seven point one that's going to be our a and B arguments I'm now going to use a new function lure P call and this takes the arguments of the Lewis stack how many arguments were providing in this case the two how many arguments we're expecting in return and the final argument is a little bit more advanced that's actually used to define how our errors handled within the Lewis script and we might look at that in a later part in this series so in a similar way that the do file function has executed the whole script the P call function has just executed the single function with the arguments that we've provided if that is all executed okay then we need to look at the result that's been returned because we have told Lua that the function is expecting two arguments it will have popped those off the stack in order to use them so we know that the top of our lewis stack will reflect the returned result of the function we've just called some going to put quite a verbose message in here c++ called in lua the add stuff function with those parameters and in return and here we use the lua to number four to turn whatever is at the top of the stack to a number and will display it so let's take a look or we can see that lure first outputs it with the print command the two parameters that we passed in will be it with some floating-point rounding and then our C program got the results ten point six which is correct and displays that so here we've often oh de dat asked to lure to go and solve of course the C program once compiled isn't easily modifiable but the lure function certainly is so instead of adding stuff let's multiply stuff straightaway no compile needed the result is different you could even go as far to make your C program sensitive to this text file changing although that's quite an advanced thing to do might leave that's also for a later video now we can call functions in Lua we can lean on Lua to start doing more complicated things so let's have a get player function into which we pass an index I've modified the Lua script to contain a small database of the players and added a get player function to return the player at a specific index in the C++ code I'm going to modify it to now find the get player function and I'm going to push a specific argument in this case 0 the player at the zeroth location I'm only going to call the function with one argument and I'm only expecting one result the player object I'm now going to borrow the code I wrote before so this time depending on the argument that I provide to lure when calling the get player function it will choose the appropriate player from its array of players let's see what happens so as before we see Squire Ciaran of we're a level twenty but if I change the code to now find the other player so I'm just changing the argument we get lured Diego of Brazil the lua script is now making decisions for us so though we've looked at C++ calling lure functions very flexible and powerful thing to be able to do because I'll forget Lua is a complete programming language you can have its own loops its own decision-making its own access to system libraries it can do whatever it needs to do but sometimes Lua may need to call functions of the host environment the C++ environment in this case so let's take a look at that I'm going to create an additional lower function I'm going to call it do a thing and it's going to take two parameters a and B so I can reuse a little bit of the code I've got already and as before I'm going to make it print out the fact that it's been called just so we can keep track but then I want it to call a function in the C++ environment I don't have a name for this function yet but I'm just going to create a variable C and I'm going to call it host function to which I'm going to pass in the parameters a and B so it does seem a little bit redundant that were creating arguments a and B in the C program passing them to lure Lua then passes them back to the C program but this is just going to demonstrate the transactions that are required in fact you know what let's just modify those slightly in the lure environment so we know that they're slightly different parameters and then want my lower function to return the computed value I'm going to modify the C++ file to call the do a thing function and as we did before I'm going to push two numbers five and six and call the function I'm going to get rid of the code displaying the players so right now let's just call the do a thing function and see what happens or we can see that do a thing was called but then we got an error at line 34 attempt to call a nil value Global host function of course we've not defined what host function is and that error don't forget was caught by a little utility function check lure that we created at the start so we need to tell the lure virtual machine what host function is and when creating a function in C++ that is callable by lua it has to follow a specific definition and it's the function name I always prefix it with a little lure so I know that the difference I'm just going to call it host function and it takes a single argument which is the lowest state what this function returns isn't the result of anything it's the number of arguments that will be passed back to lure so in this case the argument is going to be 1 the value which will ultimately be passed to see don't forget of course we don't have to pass simple values we can also pass complex table structures as well so let's do something in this function I'm going to get the values a and B that are on the Lua stack that has been provided to us but you'll notice something different I'm not indexing the minus 1 and minus 2 the way I interpret this is I've got a fresh stack to play with and it's already pushed onto that stack the two arguments that it's calling these two and it's probably good practice to make sure that the number of arguments you are expecting has actually been returned and you can do that with the lower get top function which will return how many elements are in the stack I've not done that here just to keep things a bit clear what I will add to this is a little output it's just so we can keep track of where things are up to so we can see host function has been called and then I'm going to calculate the result in this case a simple multiply I want to push back on to the Luas stack the result with the lower push number function and as I mentioned earlier I'm only pushing one thing onto the stack so that's the value that I'm returning one so this is great we've now defined a function in C++ we need to link it to the virtual machine and lure very conveniently provides a routine for us to do that Lua register and in it I provide a string name so that's what lure will actually use to call the function and I provide a function pointer to the function that we've defined and that's all we need to do so in the area where we've called the original do a think function let's display what the ultimate result is and see if all of this works so the first thing that's happened is the Dewar thing function in lure was called with the values five and six that function then went on to call host function with the values 15 and 18 and that's because that's what we've described in our lewis script to do we added 10 to the 5 and we times the 6 by 3 the host function in the c++ environment simply multiplied its two arguments together and 15 x 18 is 270 the lure function simply returned that result back to us so the C environment ultimately sees the 270 value again yes a little bit redundant but we've covered sort of all used cases here of transaction between C++ and Lua and so that's that we've really only taken a very scratch the surface look at interfacing Lua and C++ and what we've seen is it's actually not the most trivial thing in the world but it does get simpler as you get more acquainted with how the Lewis stack operates being able to enhance your program's functionality is a really cool thing to do certainly when you can start creating games and applications that can be modified by other people and that's exactly why I wanted to include this video before going to the next path of my top-down city-based car crime game series because I am actually going to be using Lua to handle game settings and mission based operations in that application but you may have noticed that this video is also tagged with the ominous hash one implying that it's part of a larger series and I think it will be though I don't know what the schedule will be because there's a lot more cool stuff you can do with lure and C++ anyway I've put some code on the github for this it's not quite exactly the same as what we've seen in the video simply because it doesn't really make much sense on its own so I tried to write an annotated form which covers all of the basic transactions between the two different environments so have a play with that and let me know on the discord if you have any difficulty getting it set up if you've enjoyed this video give me a big thumbs up and to think about subscribing and I'll see you next time take care
Info
Channel: javidx9
Views: 106,824
Rating: 4.9773254 out of 5
Keywords: one lone coder, onelonecoder, learning, programming, tutorial, c++, beginner, olcconsolegameengine, command prompt, ascii, game, game engine, lua, embedding lua in C++, scripting languages, modding, modifying code, mods, lua scripting
Id: 4l5HdmPoynw
Channel Id: undefined
Length: 35min 32sec (2132 seconds)
Published: Sun Mar 24 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.