Sweet Lua1. OverviewSweet Lua is a C++ to Lua binding library. It compiles with Microsoft Visual Studio 2008 (MSVC 9.0), MinGW (GCC 4.6.2), and Xcode (LLVM-GCC 4.2.1). Features:
1.1. Luabind vs Sweet LuaLuabind is a C++/Lua binding library written by Daniel Wallin and Arvid Nordberg. Documentation is available at http://www./products/luabind/docs.html. Luabind provides more infrastructure for object oriented programming than Sweet Lua does. Luabind provides explicit support for registering classes with the Lua environment while Sweet Lua provides support for calling member functions on objects but leaves the implementation of object orientation to the client. We believe this is a more flexible approach and allows the scripting environment to more closely match the problem domain. Luabind supports overloaded functions stored in the same value in Lua and uses best match signature matching based on the types of parameter passed when the function is called. Sweet Lua supports overloaded functions but doesn’t do signature matching so overloaded functions must be stored in different values in the Lua virtual machine. If parameters of the wrong type are passed then Sweet Lua generates an error. Luabind uses return value and parameter policies to affect the way in which return values and parameters are passed between C++ and Lua. Sweet Lua provides return value policies to generate raw, yielding, or weakening functions. Luabind depends on Boost but Sweet Lua does not. Luabind is tested on far more compilers while Sweet Lua is only tested and supported with Visual Studio 9.0. In summary Luabind offers more out of the box support for C++ style object orientation with Lua - it provides a C++ like environment in Lua. Sweet Lua doesn’t provide as much out of the box support for object orientation - it provides a Lua like environment for C++ to host - with support for the style of object orientation and execution model more in control of the C++ application developer. 1.2. SWIG vs Sweet LuaSWIG (simple wrapper interface generator) generates code to interface C++ programs with scripting languages. Documentation is available at http://www./index.php. SWIG generates C++ source code to provide bindings from C++ header files and so requires an extra conversion step in addition to compilation. Sweet Lua is written in straight C++ and so compiles as part of your source code. SWIG generates bindings for many scripting languages as well as for Lua. Sweet Lua only helps with binding to Lua. In summary SWIG is probably better for you if you want to support binding to other languages in addition to Lua and if you don’t mind adding an additional generation step to your build. 2. InstallationSweet Lua is built and installed by downloading the latest version
http://www./, extracting the archive onto your computer,
and then running " You’ll need to add the top level directory (e.g. " If you want Sweet Lua built to another location or with different variants
and/or settings then you’ll need to edit the settings in " 3. UsageSweet Lua is a C++ library for providing Lua bindings to C++ variables, functions, and objects. It provides classes to represent the Lua virtual machine (Lua), a table or object (LuaObject), and a coroutine or thread (LuaThread). The Lua class represents the Lua global environment and virtual machine. It provides functions to execute Lua scripts from files and memory and to define Lua bindings to C++ variables, functions, and objects. The LuaObject class represents an object in the Lua virtual machine. It is typically used to create prototypes or metatables in Lua that have no equivalent C++ object to associate with non-intrusively. When a LuaObject is constructed a corresponding table is created in the Lua global environment. Both the C++ application and the Lua virtual machine can then set variables on and call member functions of that table. The LuaThread class represents a Lua coroutine. It provides functions to start and resume execution in a separate yieldable thread in the Lua virtual machine. The calls can yield and return control from the virtual machine back to the program allowing asynchronous behaviour to be abstracted behind an interface that scripts can treat as synchronous. 3.1. InitializationInitializing the library is as simple as constructing a Lua object to represent the global environment and virtual machine and registering the variables, functions, and objects in the interface being bound. The library has no static state. Creating multiple Lua objects poses no problems. However, like Lua itself, objects are not thread-safe. Only one thread can call into a Lua virtual machine at once. LuaObjects and LuaThreads may only be used only with the Lua object that they were created in (as they can only represent a table or coroutine in a single Lua virtual machine). Once a Lua object has been created the values of variables can be set to the values of C++ booleans, integers, floats, strings, functions, and objects. Setting a variable to the value of a C++ function sets the value of that variable to a C++ function that decodes the arguments expected by the target C++ function from the Lua stack, makes the call to the target C++ function, and pushes any return value back onto the Lua stack. Parameters to a C++ function can be bound as upvalues when the function is registered. Simply passing a value at the time of registration stores that value as an upvalue of the registered function in the Lua virtual machine. The upvalue is then provided as a parameter when the C++ function is called by Lua. Any other parameters expected by the C++ function are mapped as per normal after the bound upvalues. Parameters to a C++ function can be bound out of order using one of the out of
order parameter values Out of order parameters and upvalue binding can be used together to provide any combination of bound values and out of order parameters. #include <sweet/lua/Lua.hpp> #include <sweet/error/ErrorPolicy.hpp> #include <stdio.h> #include <string.h> using namespace std; using namespace sweet; using namespace sweet::lua; static void print( const string& message ) { printf( "%s", message.c_str() ); } void lua_hello_world_example() { error::ErrorPolicy error_policy; Lua lua( error_policy ); lua.globals() ( "print", &print ) ; const char* script = "print('Hello World!\\n')"; lua.call( script, script + strlen(script), "hello_world" ).end(); } 3.2. Scripts and FunctionsScripts and functions are executed by calling All of these functions return a helper object that provides a convenient syntax
for supplying the parameters to the call and getting the return value via
#include <sweet/lua/Lua.hpp> #include <sweet/error/ErrorPolicy.hpp> #include <string.h> using namespace sweet; using namespace sweet::lua; void lua_factorial_example() { error::ErrorPolicy error_policy; Lua lua( error_policy ); const char* script = "function factorial(n) \n" " if n <= 1 then \n" " return 1 \n" " else \n" " return n * factorial(n - 1) \n" " end \n" "end" ; lua.call( script, script + strlen(script), "factorial" ) .end(); int factorial = 0; lua.call( "factorial" ) ( 4 ) .end( &factorial ); SWEET_ASSERT( factorial == 24 ); } 3.3. ObjectsBy default C++ objects are stored in Lua as user data. C++ objects passed to Lua are copy constructed into user data values. When these user data values are garbage collected the destructor for the C++ object is automatically called and the memory allocated for them is freed. More usefully C++ objects can be stored in Lua as tables. C++ objects stored as tables are automatically converted to and from their equivalent table when passed to and from Lua. The C++ class of objects to be represented by reference must have its
storage type trait defined to LuaReference using the
Lua tables that are associated with C++ objects are explicitly created and
destroyed by users of the library through calls to Once the table has been created its fields can be set to the values of C++ booleans,
integers, floats, strings, objects, and functions. The fields can be cleared by
setting their value to nil. See Two important fields that must always be set are the The value of a variable can be set to a member function. The first parameter of
these calls is always assumed to provide the this pointer of the object to make
the call on. This works with Lua’s mechanism of implicitly providing the Member functions that have been defined in a Lua script (using the ":" syntax
or that take an explicit self parameter) on an object that corresponds to a
C++ object can be called using the overload of the Lua::call() function that
takes an object as its second parameter. This looks up the function as a
member of the object’s table but doesn’t pass the object’s table as the
implicit first parameter to the call (the #include <sweet/lua/Lua.hpp> #include <sweet/error/ErrorPolicy.hpp> #include <stdio.h> #include <string.h> using namespace std; using namespace sweet; using namespace sweet::lua; enum LogMask { LOG_NULL = 0x00, LOG_ERROR = 0x01, LOG_WARN = 0x02 }; class Log { int level_; public: Log( int level ) : level_( level ) { } void set_level( int level ) { level_ = level; } int get_level() const { return level_; } void log( int level, const std::string& format ) { if ( level_ & level ) { printf( "%s\n", format.c_str() ); } } }; SWEET_LUA_TYPE_CONVERSION( Log, LuaByReference ); void lua_logging_example() { error::ErrorPolicy error_policy; Lua lua( error_policy ); Log log( LOG_NULL ); lua.create( log ); lua.members( log ) .type( SWEET_STATIC_TYPEID(Log) ) .this_pointer( &log ) ( "LOG_NULL", int(LOG_NULL) ) ( "LOG_ERROR", int(LOG_ERROR) ) ( "LOG_WARN", int(LOG_WARN) ) ( "set_level", &Log::set_level ) ( "get_level", &Log::get_level ) ( "log", &Log::log ) ; lua.globals() ( "log", log ) ; const char* script = "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n" "log:log( log.LOG_ERROR, 'This is an error' );\n" "log:log( log.LOG_WARN, 'This is a warning' );\n" ; lua.call( script, script + strlen(script), "logging" ).end(); } 3.4. Classes and PrototypesLua is a prototype based language. A prototype defines the behaviour of an object in a prototype based language. The relationship between an object and its prototype in a prototype based language is similar to the relationship between an object and a class in C++. The prototype of an object is another object that index operations that fail on
the first object are resolved on. For example if a script calls the For more descriptions of object oriented programming in Lua and prototype based languages in general have a look at:
Prototypes can be provided using the library by creating a LuaObject to use
as the prototype table and a LuaObject to use as the metatable that redirects
failed index operations to the prototype. One or more objects are then set to
use that prototype by setting their metatable to be the metatable created by
the second LuaObject. See Note that in the following example the prototype table stores the #include <sweet/lua/Lua.hpp> #include <sweet/lua/LuaObject.hpp> #include <sweet/error/ErrorPolicy.hpp> #include <stdio.h> #include <string.h> using namespace std; using namespace sweet; using namespace sweet::lua; enum LogMask { LOG_NULL = 0x00, LOG_ERROR = 0x01, LOG_WARN = 0x02 }; class Log { int level_; public: Log( int level ) : level_( level ) { } void set_level( int level ) { level_ = level; } int get_level() const { return level_; } void log( int level, const std::string& format ) { if ( level_ & level ) { printf( "%s\n", format.c_str() ); } } }; SWEET_LUA_TYPE_CONVERSION( Log, LuaByReference ); void lua_logging_prototype_example() { error::ErrorPolicy error_policy; Lua lua( error_policy ); LuaObject log_prototype( lua ); log_prototype.members() .type( SWEET_STATIC_TYPEID(Log) ) ( "LOG_NULL", static_cast<int>(LOG_NULL) ) ( "LOG_ERROR", static_cast<int>(LOG_ERROR) ) ( "LOG_WARN", static_cast<int>(LOG_WARN) ) ( "set_level", &Log::set_level ) ( "get_level", &Log::get_level ) ( "log", &Log::log ) ; LuaObject log_metatable( lua ); log_metatable.members() ( "__index", log_prototype ) ; Log log( LOG_NULL ); lua.create( log ); lua.members( log ) .metatable( log_metatable ) .this_pointer( &log ) ; lua.globals() ( "log", log ) ; const char* script = "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n" "log:log( log.LOG_ERROR, 'This is an error' );\n" "log:log( log.LOG_WARN, 'This is a warning' );\n" ; lua.call( script, script + strlen(script), "logging" ).end(); } 3.5. Weak ObjectsOften an application will give Lua script ownership of the lifetime of the objects that it creates. The simplest way to do this is to store an object by value in the Lua table to manage the lifetime of the C++ object (for example a reference counted pointer back to the original C++ object). This unfortunately causes a memory leak because the C++ object is associated with the table through a strong reference stored in the Lua registry at the same time as the table is referencing the C++ object via a reference counted smart pointer or some other mechanism that maintains a strong reference. This cycle allows the C++ object and the Lua table to live forever. To break the cycle the table can be stored in a special weak objects table. This weakens the reference from the C++ object to the Lua table allowing Lua to garbage collect the table when it is no longer referenced (because the C++ side is no longer holding a strong reference to it only the references that Lua itself holds are counted towards keeping it alive) and when this happens, provided that the lifetime management object (e.g. the reference counted pointer) has an appropriate destructor, the C++ object will also be destroyed. Objects are weakened when returned from C++ functions that have been
registered using the The typical implementation of this uses shared_ptrs with a custom deleter to
ensure that #if defined SWEET_BOOST_ENABLED #include <sweet/lua.hpp> #include <sweet/lua/shared_ptr.hpp> using namespace boost; using namespace sweet::lua; class Log; SWEET_LUA_TYPE_CONVERSION( Log, LuaReference ); enum LogMask { LOG_NULL = 0x00, LOG_ERROR = 0x01, LOG_WARN = 0x02 }; class Log { int level_; public: Log( int level ) : level_( level ) { } void set_level( int level ) { level_ = level; } int get_level() const { return level_; } void log( int level, const std::string& format ) { if ( level_ & level ) { printf( "%s\n", format.c_str() ); } } }; struct LogCreator { Lua* lua_; LuaObject* log_metatable_; LogCreator( Lua* lua, LuaObject* log_metatable ) : lua_( lua ), log_metatable_( log_metatable ) { } shared_ptr<Log> create() { struct LogDeleter { Lua* lua_; LogDeleter( Lua* lua ) : lua_( lua ) { } void operator()( Log* log ) const { lua_->destroy( *log ); delete log; log = NULL; } }; shared_ptr<Log> log( new Log(LOG_NULL), LogDeleter(lua_) ); lua_->create( *log ); lua_->members( *log ) .metatable( *log_metatable_ ) .this_pointer( log.get() ) ( "ptr", value(log) ) ; return log; } }; void lua_logging_owned_by_lua_example() { Lua lua; LuaObject log_prototype( lua ); log_prototype.members() .type( SWEET_STATIC_TYPEID(Log) ) ( "LOG_NULL", static_cast<int>(LOG_NULL) ) ( "LOG_ERROR", static_cast<int>(LOG_ERROR) ) ( "LOG_WARN", static_cast<int>(LOG_WARN) ) ( "set_level", &Log::set_level ) ( "get_level", &Log::get_level ) ( "log", &Log::log ) ; LuaObject log_metatable( lua ); log_metatable.members() .type( SWEET_STATIC_TYPEID(LuaObject) ) ( "__index", log_prototype ) ; LogCreator log_creator( &lua, &log_metatable ); lua.globals() ( "Log", weaken(&LogCreator::create), &log_creator ) ; const char* script = "local log = Log();\n" "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n" "log:log( log.LOG_ERROR, 'This is an error' );\n" "log:log( log.LOG_WARN, 'This is a warning' );\n" ; lua.call( script, script + strlen(script), "logging", 0 ).end(); } #else void lua_logging_owned_by_lua_example() { } #endif 3.6. CoroutinesThe library provides support for coroutines through the LuaThread object and
the The LuaThread object is constructed just like a LuaObject. Once constructed it then behaves similarly to a Lua object except that it can resume functions as well as call them. The 3.7. Standard Template Library IntegrationThere is a small amount of integration with the Standard Template Library. The standard containers vector, list, set, multiset, map, and multimap can all be implicitly converted into Lua iterators that iterates over their [begin, end) sequences. There is also a #include <sweet/lua/Lua.hpp> #include <sweet/lua/vector.hpp> #include <sweet/rtti/macros.hpp> #include <sweet/error/ErrorPolicy.hpp> #include <string> #include <string.h> using std::string; using std::vector; using namespace sweet; using namespace sweet::lua; struct Target { vector<string> values_; Target() : values_() { } void add_value( const string& value ) { values_.push_back( value ); } const vector<string>& get_values() const { return values_; } }; SWEET_LUA_TYPE_CONVERSION( Target, LuaByReference ); void lua_vector_example() { Target target; target.add_value( "one" ); target.add_value( "two" ); target.add_value( "three" ); error::ErrorPolicy error_policy; Lua lua( error_policy ); lua.create( target ); lua.members( target ) .type( SWEET_STATIC_TYPEID(Target) ) .this_pointer( &target ) ( "get_values", &Target::get_values ) ; lua.globals() ( "target", target ) ; const char* script = "for v in target:get_values() do print(v) end;"; lua.call( script, script + strlen(script), "log" ).end(); } 3.8. Boost IntegrationThere is a small amount of integration with the Boost Filesystem library. The
#include <sweet/lua/filesystem.hpp>
Copyright (C) 2006 - 2013 Charles Baker. All rights reserved.
|
|