by date
Fun with Lua
Fun with Lua
| Name: | Tom Bampton | ![]() |
|---|---|---|
| Date Posted: | Jul 25, 2007 | |
| Rating: | 3.5 out of 5 | |
| Public: | YES | |
| Comments: | YES | |
| RSS Feed: | or Subscribe with . | |
| Profile Page: | View profile page for Tom Bampton |
Blog post
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++.
Recent Blog Posts
| List: | 08/20/07 - GID23 and NPC Editor 07/25/07 - Fun with Lua 06/11/07 - How NOT to make a game 11/18/06 - Thinking Outside the Box 11/03/06 - Alive and Ticking: Now with exploding ants 10/28/06 - Fun with zips 10/02/06 - Alive and Ticking gets to Beta .... err, almost. (Warning: Screenshot Heavy) 09/08/06 - Internal Name Operator |
|---|
Submit your own resources!| Anthony Rosenbaum (Jul 25, 2007 at 20:30 GMT) |
| Joe Rossi (Jul 25, 2007 at 21:22 GMT) |
| Rex (Jul 25, 2007 at 21:49 GMT) |
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
Edited on Jul 25, 2007 21:53 GMT
| Tom Bampton (Jul 25, 2007 at 23:02 GMT) |
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.
| Joe Rossi (Jul 26, 2007 at 00:56 GMT) |
| Tom Bampton (Jul 26, 2007 at 01:17 GMT) |
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.
| Tom Spilman (Jul 26, 2007 at 02:00 GMT) |
Also if the Lua engine is exposed to the TelnetDebugger we could possibly eventual support it in Torsion.
Edited on Jul 26, 2007 02:03 GMT
| Tom Bampton (Jul 26, 2007 at 02:21 GMT) |
Hmm. Interesting point about the debugger. I'll have to look into that.
T.
| Ross Pawley (Jul 26, 2007 at 06:50 GMT) |
| Joe Rossi (Jul 30, 2007 at 03:13 GMT) |
| Ross Pawley (Nov 30, 2007 at 07:03 GMT) |
| Tom Bampton (Nov 30, 2007 at 07:11 GMT) |
T.
| Ross Pawley (Nov 30, 2007 at 07:19 GMT) |
You must be a member and be logged in to either append comments or rate this resource.



3.5 out of 5


