Fun with Lua
by Tom Bampton · 07/25/2007 (12:19 pm) · 20 comments
Lua is a great little language. It's small and simple, yet powerful and flexible when you know how to use it. Embedding a Lua interpreter into an arbitrary application is easy, if requiring a little code verbosity.
There are many existing ways to expose parts of an application to Lua. However, most of them involve either writing a lot of extra code or running some external pre-processor to generate the extra code for you. For me, that is way too much extra work and additional points of failure.
Of course, if you wan't to expose C++ code to script, then it obviously requires some additional code that has to be written somehow. Whilst the external pre-processor route removes most of the impetus from the programmer, it requires another step in compilation and feels somewhat error prone. There are also meta programming libraries which do much the same thing, but make the C++ compiler do the work instead of an external pre-processor. They still require more re-definition than I was willing to put up with.
Wouldn't it be nice if you could make the C++ compiler do all the work for you ?
It was with that question in mind that I sat down last weekend determined to make it require no more then 1 line of additional code per function or method I wanted to expose to Lua. Oh, and I wanted to be able to do the same for calling Lua functions from C++.
Calling Lua from C++
Consider this Lua function:
If I wanted to call this from C++, I'd have to do something along the lines of:
Bit of a pain, right? Through some nifty template magic, I add one line of code to the application:
And I can then call the Lua function as follows:
As an added bonus, I get full compile time type checking as if it was a normal C++ function.
Calling C++ from Lua
Calling C++ from Lua can be even more of a pain in the butt. You have to implement a function that gets the arguments from the Lua stack and then calls whatever existing C++ function you want. It's not rocket science, but it's boring and means you effectively have to implement two functions for every function you want callable from Lua.
So, instead, through more template magic, I do:
Then I can call it from Lua as follows:
Again, this is fully type checked and it only requires one extra line of code per function.
C++ Objects
Things get even more complicated when you want to expose a whole C++ object to Lua. Doing it by hand is generally not feasible, so you'd either have to use an external application or a metaprogramming library to do it. Either way is too much effort for my liking.
Which gives you an object that can be created in C++ or Lua and used/passed around in both. Using it from Lua would look something like:
What's the point ?
The code will port to Torque reasonably easily, which would make ConsoleFunction() and ConsoleMethod() obsolete. Instead of writing a console method and a C++ method, you'd just write the C++ method and 1 additional line of code to expose it to script.
There would be no more worrying about converting things to or from strings, as that would all be done automatically. There would also be no more functions or methods that behaved differently depending on whether you called them from C++ or script. Oh, and all those useful console functions you currently can't call from C++, would be callable from both C++ and script.
Furthermore, since none of the implementation behind the templates or macros is exposed, the same interfaces could be used with any scripting language. This would mean that replacing TorqueScript with Lua would be a pretty simple matter of changing the wrappers and recompiling. That, of course, assumes that Torque has been fully moved over to the wrapper.
Whilst the Lua specific version of this is being used "now", the Torque version of it is a little more long term. I'll be starting the port once the Lua interfaces are more complete, but changing all the existing ConsoleMethods and ConsoleFunctions will be time consuming. It will also mean that the semantics of some functions/methods will have to change; there's quite a few that behave slightly differently depending on whether they are called from script or C++.
There are many existing ways to expose parts of an application to Lua. However, most of them involve either writing a lot of extra code or running some external pre-processor to generate the extra code for you. For me, that is way too much extra work and additional points of failure.
Of course, if you wan't to expose C++ code to script, then it obviously requires some additional code that has to be written somehow. Whilst the external pre-processor route removes most of the impetus from the programmer, it requires another step in compilation and feels somewhat error prone. There are also meta programming libraries which do much the same thing, but make the C++ compiler do the work instead of an external pre-processor. They still require more re-definition than I was willing to put up with.
Wouldn't it be nice if you could make the C++ compiler do all the work for you ?
It was with that question in mind that I sat down last weekend determined to make it require no more then 1 line of additional code per function or method I wanted to expose to Lua. Oh, and I wanted to be able to do the same for calling Lua functions from C++.
Calling Lua from C++
Consider this Lua function:
function addSomething(a, b) return a + b end
If I wanted to call this from C++, I'd have to do something along the lines of:
lua_State *L; // Defined somewhere lua_pushnumber(L, 42); lua_pushnumber(L, 69); if(lua_pcall(L, 2, 1, 0)) != 0) // ... handle error ... lua_Number ret = luaL_checknumber(L, -1); lua_pop(L, 1);
Bit of a pain, right? Through some nifty template magic, I add one line of code to the application:
CppLuaDelegate<lua_Number, lua_Number, lua_Number> CppLuaFuncImpl(addSomething);
And I can then call the Lua function as follows:
lua_Number ret = addSomething(42, 69);
As an added bonus, I get full compile time type checking as if it was a normal C++ function.
Calling C++ from Lua
Calling C++ from Lua can be even more of a pain in the butt. You have to implement a function that gets the arguments from the Lua stack and then calls whatever existing C++ function you want. It's not rocket science, but it's boring and means you effectively have to implement two functions for every function you want callable from Lua.
So, instead, through more template magic, I do:
lua_Number addSomething(lua_Number a, lua_Number b)
{
return a + b;
}
LuaCppDelegate<lua_Number, lua_Number, lua_Number> LuaCppFuncImpl(addSomething);Then I can call it from Lua as follows:
ret = addSomething(42, 69)
Again, this is fully type checked and it only requires one extra line of code per function.
C++ Objects
Things get even more complicated when you want to expose a whole C++ object to Lua. Doing it by hand is generally not feasible, so you'd either have to use an external application or a metaprogramming library to do it. Either way is too much effort for my liking.
class LuaTestA : public LuaObject
{
typedef LuaObject Parent;
public:
lua_Integer mValue;
LuaTestA() : mValue(0) {}
DeclareLuaObject(LuaTestA);
virtual void SetValue(lua_Integer val) { mValue = val; }
virtual lua_Integer GetValue() const { return mValue; }
};
ImplementLuaObject(LuaTestA);
LuaCppMethod<LuaTestA, void, lua_Integer> MethodImpl(LuaTestA, SetValue);
LuaCppMethod<LuaTestA, lua_Integer> MethodImpl(LuaTestA, GetValue);Which gives you an object that can be created in C++ or Lua and used/passed around in both. Using it from Lua would look something like:
foo = LuaTestA.new() foo:SetValue(42) print(foo:GetValue())
What's the point ?
The code will port to Torque reasonably easily, which would make ConsoleFunction() and ConsoleMethod() obsolete. Instead of writing a console method and a C++ method, you'd just write the C++ method and 1 additional line of code to expose it to script.
There would be no more worrying about converting things to or from strings, as that would all be done automatically. There would also be no more functions or methods that behaved differently depending on whether you called them from C++ or script. Oh, and all those useful console functions you currently can't call from C++, would be callable from both C++ and script.
Furthermore, since none of the implementation behind the templates or macros is exposed, the same interfaces could be used with any scripting language. This would mean that replacing TorqueScript with Lua would be a pretty simple matter of changing the wrappers and recompiling. That, of course, assumes that Torque has been fully moved over to the wrapper.
Whilst the Lua specific version of this is being used "now", the Torque version of it is a little more long term. I'll be starting the port once the Lua interfaces are more complete, but changing all the existing ConsoleMethods and ConsoleFunctions will be time consuming. It will also mean that the semantics of some functions/methods will have to change; there's quite a few that behave slightly differently depending on whether they are called from script or C++.
About the author
#2
07/25/2007 (2:22 pm)
Lua is great! If you haven't seen it I made a resource to bridge Lua and Torquscript, allowing them to call each others code and access each others variables. www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=9665 I'm using Lua heavily in my project (a turn based RPG) and I love it, about all of my game logic code is in Lua. So I'm interested in what you are doing here. It could make it possible to create new TGB sceneObjects inside of lua scripts, amongst other things.
#3
I would definitely pay to have something like this worked upon...;), as, Tom, you seem to have a very good grip on it...
LUAdocuments@fragMO
07/25/2007 (2:49 pm)
fragMOTION utilizes LUA as a scripting language. FragMOTION is a very lowcost modeling and animation program; that happens to have a solid handshake to Milkshape3D[thus DTS/DSQ]. I've been trying to get someone at fragMO site to look into some animation scripting, perhaps this scripting language could even help produce a DTS/DSQ exporter? Right now, I'm just trying to get my vertex weights output to some sort of table for mass viewing of the data instead of relying upon the UI's selection mode and then looking at a small dialog with only a limited scheme for viewing the selected vertice...I would definitely pay to have something like this worked upon...;), as, Tom, you seem to have a very good grip on it...
LUAdocuments@fragMO
#4
Probably never ... I have no use for Javascript ;) That said, the only use I had for Basic was as a joke, so you never know ;)
@Joe,
Yeh, I saw that a while back, but didn't like it. It requires too much effort to use and ignores the strengths of both scripting languages. It also quits Torque entirely if there's an error in a Lua script, which is unnecessary and silly. Perhaps I was biased because I already had a Lua wrapper that worked similarly to the way ConsoleObjects work, though that too had downsides. For example, you still had to deal with the Lua stack in function/method implementations.
Personally, I think that any replacement/additional scripting language has to provide at a minimum roughly the same features as TorqueScript, with the same degree of accessibility. To do otherwise is just a step backwards and seems somewhat pointless.
I am not trying to flame/annoy/etc you there, it's just an honest critique of your implementation. Clearly, it works for you and you like it, so that's cool.
@Rex,
If fragMOTION uses Lua as a DLL, it's probably possible to wrap the DTS SDK such that it could be loaded with require() and then accessed through Lua. That said, I only have spare time for my own projects now so any contract work would mean putting them aside and losing some weekends. Thus, I couldn't do it for indie prices. Still, if you want to discuss it, feel free to email me.
T.
07/25/2007 (4:02 pm)
@Anthony,Probably never ... I have no use for Javascript ;) That said, the only use I had for Basic was as a joke, so you never know ;)
@Joe,
Yeh, I saw that a while back, but didn't like it. It requires too much effort to use and ignores the strengths of both scripting languages. It also quits Torque entirely if there's an error in a Lua script, which is unnecessary and silly. Perhaps I was biased because I already had a Lua wrapper that worked similarly to the way ConsoleObjects work, though that too had downsides. For example, you still had to deal with the Lua stack in function/method implementations.
Personally, I think that any replacement/additional scripting language has to provide at a minimum roughly the same features as TorqueScript, with the same degree of accessibility. To do otherwise is just a step backwards and seems somewhat pointless.
I am not trying to flame/annoy/etc you there, it's just an honest critique of your implementation. Clearly, it works for you and you like it, so that's cool.
@Rex,
If fragMOTION uses Lua as a DLL, it's probably possible to wrap the DTS SDK such that it could be loaded with require() and then accessed through Lua. That said, I only have spare time for my own projects now so any contract work would mean putting them aside and losing some weekends. Thus, I couldn't do it for indie prices. Still, if you want to discuss it, feel free to email me.
T.
#5
07/25/2007 (5:56 pm)
Tom I'm sorry that you don't like it. I know it's not very efficient and sometimes difficult to debug, but it works like a charm over here.
#6
No need to be sorry. Without good feedback, nothing improves. Just saying something is great (or sucks) doesn't do anyone any good unless all you want is ego stroking.
On the difficult to debug thing, one thing I found a while back is it's easy to forget that lua_pcall() also returns an error message if it fails. It returns it as a string on the top of the Lua stack, so you need to lua_tostring(L, -1); to get it. It becomes much easier to figure out what's going wrong when you use the error message instead of just saying "syntax error" ;)
T.
07/25/2007 (6:17 pm)
Joe,No need to be sorry. Without good feedback, nothing improves. Just saying something is great (or sucks) doesn't do anyone any good unless all you want is ego stroking.
On the difficult to debug thing, one thing I found a while back is it's easy to forget that lua_pcall() also returns an error message if it fails. It returns it as a string on the top of the Lua stack, so you need to lua_tostring(L, -1); to get it. It becomes much easier to figure out what's going wrong when you use the error message instead of just saying "syntax error" ;)
T.
#7
Also if the Lua engine is exposed to the TelnetDebugger we could possibly eventual support it in Torsion.
07/25/2007 (7:00 pm)
Don't forget script function documentation. It is still extremely useful to be able to dump the exports with docs at runtime.Also if the Lua engine is exposed to the TelnetDebugger we could possibly eventual support it in Torsion.
#8
Hmm. Interesting point about the debugger. I'll have to look into that.
T.
07/25/2007 (7:21 pm)
Yeh, I will be adding script docs. Just couldnt be bothered with the first pass since i was more worried about getting the templates working ... which was quite hairy ;)Hmm. Interesting point about the debugger. I'll have to look into that.
T.
#9
07/25/2007 (11:50 pm)
Very cool stuff here. Lua rocks. Can't wait to see further developments.
#10
07/29/2007 (8:13 pm)
Thanks Tom, my Lua resource has been updated to report more useful errors if anyone wants to try it ;)
#11
11/29/2007 (11:03 pm)
@Tom, any update on this? A resource perhaps?
#12
T.
11/29/2007 (11:11 pm)
It's been improved somewhat since this resource, but I never got around to porting it to Torque. Thus, a resource wouldn't be that useful :)T.
#13
11/29/2007 (11:19 pm)
By porting to Torque, do you mean using the same template based methods for TorqueScript, or just putting what you have into Torque in general? If the former, I'm personally still interested, and if the latter and you're willing, I wouldn't mind trying my hand at porting it myself.
#14
12/02/2011 (4:11 pm)
@Tom: What happened to this? It seems like you've got a rather elegant way to weld Torque and Lua together, but I'm not seeing a resource or other source for the template delegate definitions. Is the source code still available?
#15
Wow, this is an old post :) Apparently email notifications still work, else I wouldn't have seen your post.
There's no resource for Torque, and I don't think I ever got round to integrating it with Torque. I don't think the mentioned code exists anymore.
We do have a rewritten / evolved version of it that supports a lot more stuff, including properties and components. It would, however, be a lot harder to integrate with Torque and there's currently have no motivation to do so.
If you are interested in something along these lines, I'd suggest having a look at LuaBind since it works similarly. I don't know how easy it would be to integrate with Torque, though. There were reasons that we didn't use LuaBind, but I think they were mostly specific to our intended use of Lua so not sure they're relevant.
T.
12/02/2011 (11:35 pm)
Kevin,Wow, this is an old post :) Apparently email notifications still work, else I wouldn't have seen your post.
There's no resource for Torque, and I don't think I ever got round to integrating it with Torque. I don't think the mentioned code exists anymore.
We do have a rewritten / evolved version of it that supports a lot more stuff, including properties and components. It would, however, be a lot harder to integrate with Torque and there's currently have no motivation to do so.
If you are interested in something along these lines, I'd suggest having a look at LuaBind since it works similarly. I don't know how easy it would be to integrate with Torque, though. There were reasons that we didn't use LuaBind, but I think they were mostly specific to our intended use of Lua so not sure they're relevant.
T.
#16
12/03/2011 (5:21 am)
My bridge still works. Even though Tom doesn't care for it, it does allow us to use Lua in conjunction with Torque. My game logic is practically entirely written in Lua.
#17
Something that works is infinitely better than something that doesn't exist ;)
T.
12/03/2011 (5:36 am)
Joe,Something that works is infinitely better than something that doesn't exist ;)
T.
#18
@Joe: I'm mostly exploring my options at the moment; there's a lot of Lua code -- libraries, etc. -- out there that I'd like to take advantage of. (Ditto for Python, but Lua is faster than Python; I'd prefer Lua.) So I'm looking more at completely replacing Torquescript, but your solution might work just fine.
12/05/2011 (8:51 am)
@Tom: Thanks for the update. I take it that you're not willing to share your code, but I'll have to take a look at LuaBind.@Joe: I'm mostly exploring my options at the moment; there's a lot of Lua code -- libraries, etc. -- out there that I'd like to take advantage of. (Ditto for Python, but Lua is faster than Python; I'd prefer Lua.) So I'm looking more at completely replacing Torquescript, but your solution might work just fine.
#19
12/06/2011 (9:10 am)
You can still use Lua libraries. All my code does is embed an interpreter in the game exe and exposes functions that allow the two languages to execute each others code and scripts.
#20
Since it was written for internal use, releasing the code would involve quite a lot of work to separate it from our internal libraries. We would, actually, like to release at least parts of those libs (including the Lua code) at some point. However, they're not ready and the work involved to get them to a releasable point is quite high.
We are in the final crunch time on a game that will ship in early January, so there's really no time for anything besides getting the game done. We also have a contract starting immediately after that. Whilst I'd love it if I could just do whatever I wanted, priority has to go to paying work otherwise we don't eat :)
T.
12/08/2011 (3:18 am)
Kevin,Since it was written for internal use, releasing the code would involve quite a lot of work to separate it from our internal libraries. We would, actually, like to release at least parts of those libs (including the Lua code) at some point. However, they're not ready and the work involved to get them to a releasable point is quite high.
We are in the final crunch time on a game that will ship in early January, so there's really no time for anything besides getting the game done. We also have a contract starting immediately after that. Whilst I'd love it if I could just do whatever I wanted, priority has to go to paying work otherwise we don't eat :)
T.

Associate Anthony Rosenbaum