How to bind a C ++ object to Lua lightweight

Source: Internet
Author: User

GamesThe script language has become a standard application. The script language canGamesDevelopers play an important role in Data structuring, planning events, testing, and debugging. The scripting language can also allow non-procedural experts like art to use a high-level abstract scriptGamesWrite code. Part of this abstraction layer can also be provided to players to customize the entireGames.

From the programmer's point of view, the main problem in embedding a scripting language into a game is that if the scripting language provides access to the host object, it is usually a C/C ++ object ). When selecting a scripting language, there are two key features: embedding and binding. These are some of the original designs of Lua. However, the Lua language does not provide any tools for automatic binding, because this is due to another original design intention: Lua only provides a mechanism, not a policy.
 
Therefore, there are many policies that can be used inLuaTo bind the Host object. Each policy has its advantages and disadvantages,GamesDevelopers must determine the best policy after obtaining the functional requirements required in the script environment. Some developers may only map the C/C ++ object to a simple value, but others may need to implement the runtime type check mechanism, or even expand the Host application in Lua. Another important issue to be addressed is whether to allow Lua to control the lifecycle of the Host object. In this article, we will explore how to useLuaAPI to implement different hostsObjectBinding policy.

Bind a function

To illustrate the implementation of different policies, let's consider binding a simple C ++ class to Lua. The goal is to implement class access in Lua. Therefore, the script is allowed to use the services provided by the host through the exported functions. The main idea here is to use a simple class to guide our discussion. The following is a fictional hero class in a game. Several of them will be mapped to the public methods in Lua.

 
 
  1. class Hero{  
  2. public:  
  3.  Hero( const char* name );  
  4.  ~Hero();  
  5.  const char* GetName();  
  6.  void SetEnergy( double energy );  
  7.  double GetEnergy();  
  8. }; 

To bind class methods to Lua, we must use the Lua API to compile the binding function. Each binding function is responsible for receiving Lua values as input parameters, converting them into corresponding C/C ++ values, and calling the actual functions or methods, return the returned values to Lua. In the standard release version of Lua, The Lua API and the auxiliary library provide many convenient functions to convert the Lua to C/C ++ values. Similarly, it also provides functions for the conversion from C/C ++ to Lua values. For example, luaL_checknumber provides the function of converting input parameters to corresponding floating point values.

If the parameter cannot correspond to the value type in Lua, the function throws an exception. In contrast, lua_pushnumber adds the given floating point value to the top of the Lua parameter stack. There are also a series of similar functions to map other basic Lua types and C/C ++ data types. At present, we propose different strategies to expand the standard Lua library and its functions for converting C/C ++ objects. To use C ++, let's create a class called Binder to encapsulate the function of converting values between Lua and host objects. This class also provides a method for initializing modules to be exported to Lua.

 
 
  1. Class Binder
  2. {
  3. Public:
  4. // Constructor
  5. Binder (lua_state * L );
  6. // Module library) initialization
  7. Int init (const char * tname, const luaL_reg * first );
  8. // Map Basic Types
  9. Void pushnumber (double v );
  10. Double checknumber (int index );
  11. Void pushstring (const char s );
  12. Const char * checkstring (int index );
  13. ....
  14. // Ing user-defined types
  15. Void pushusertype (void * udata, const char * tname );
  16. Void * checkusertype (int index, const char * tname );
  17. };

Class constructor receives Lua_state to map objects. The initialization function receives the restricted type name and is also represented as the Library name. A global variable name is used to represent the class table in Lua), and calls the standard Lua library directly. For example, the method for ing a value to Lua or ing from Lua may be as follows:

 
 
  1. void Binder::pushnumber( double v )  
  2. {  
  3.  lua_pushnumber( L,v );  
  4. }  
  5.  
  6. double Binder::checknumber( int index )  
  7. {  
  8.  return luaL_checknumber( L,index );  

The real challenge is the conversion of user-defined types: pushusertype and checkusertype. These methods must ensure that the binding policy of the ing object is consistent with that currently in use. Each policy requires different library loading methods, so different implementations of the initialization method init should be given.

Once we have a binder implementation, the code for binding functions is very easy to write. For example, the constructor and destructor of classes related to function binding are as follows:

 
 
  1. static int bnd_Create( lua_state* L ){  
  2.  LuaBinder binder(L);  
  3. Hero* h = new Hero(binder.checkstring(L,1));  
  4. binder.pushusertype(h,”Hero”);  
  5. return i;  
  6. }  
  7.  
  8. static int bnd_Destroy( lua_state* L ){  
  9.  LuaBinder binder(L);  
  10.  Hero * hero = (Hero*)binder.checkusertype( 1, “Hero” );  
  11.  delete hero;  
  12.  return 0;  

Similarly, the binding functions of the GetEnergy and SetEnergy methods can be encoded as follows:

 
 
  1. static int bnd_GetEnergy( lua_state* L ){  
  2.  LuaBinder binder(L);  
  3.  Hero* hero = (Hero*)binder.checkusertype(1,”Hero”);  
  4.  binder.pushnumber(hero->GetEnergy());  
  5.  return 1;  
  6. }  
  7. static int bnd_SetEnery( lua_State* L ){  
  8.  LuaBinder binder(L);  
  9.  Hero* hero = (Hero*)binder.checkusertype(1,”Hero”);  
  10.  Hero.setGetEnergy( binder.checknumer(2) );  
  11.  return 1;  

Note that the encapsulation policy of the bound function will be used for ing.Object: The Host object uses the corresponding check and push method groups for ing. These methods are also used to receive Association types as input parameters. Complete encoding for all the binding functions. We can compile the method to open the Library:

 
 
  1. static const luaL_reg herolib[] = {  
  2.  { “Create”, bnd_Create },  
  3.  {“Destroy”, bnd_Destory },  
  4.  {“GetName”, bnd_GetName},  
  5.  …  
  6. };  
  7. int luaopen_hero( lua_State *L ) {  
  8.  LuaBinder binder(L);  
  9.  Binder.init( “hero”, herolib );  
  10.  return i;  

Bind host object and Lua Value

Put the C/C ++ object andLuaThe binding method is to map its memory address to lightweight user data. A lightweight user data can be expressed as void * using a pointer. In Lua, it is just a common value. In the script environment, you can get the value of an object, compare it, and send it back to the host. The method corresponding to this policy implemented in the binder class is implemented by directly calling the functions already implemented in the standard library:

 
 
  1. void Binder::init( const char *tname, const luaL_reg *flist ){  
  2.  luaL_register( L, tname, flist );  
  3. }  
  4. void Binder::pushusertype( void* udata, const char* tname ){  
  5.  lua_pushlightuserdata( L, udata );  
  6. }  
  7. void *Binder::checkusertype( int index, const char* tname ){  
  8.  void *udata = lua_touserdata( L, index );  
  9.  if ( udata ==0 ) luaL_typerror( L, index, tname );  
  10.  return udata;  

The luaL_typerror function is used to throw an exception in the preceding implementation, indicating that the input parameter does not have a valid related object.

Through this ing of our hero class policy, the following Lua is available:

 
 
  1. Local h = Hero.Create(“myhero”)  
  2. Local e = Hero.GetEnergy(h)  
  3. Hero.SetEnergy(h, e-1)  
  4. Hero.Destroy() 

SetObjectIng to a simple value has at least three advantages: simple, efficient, and small memory overwrite. As we have seen above, this strategy is straightforward, and communication between Lua and the host language is also the most efficient, this is because it does not introduce any indirect access and memory allocation. However, as an implementation, this simple policy becomes insecure because the user data value is always regarded as a valid parameter. Passing in any invalid object will cause the host Program to crash directly.

Join type check

We can implement a simple real-time type check mechanism to avoidLuaThe host program crashes in the environment. Of course, adding a type check will reduce the efficiency and increase the memory usage. If the script is only used in the development phase of the game, the type check mechanism can be disabled until it is released.
 
In other words, if the script tool is to be provided to end users, the type check becomes very important and must be released together with the product.

To add the type check mechanism to our binding to the value policy, we can createObjectAndLuaTables mapped to corresponding type names. In all the strategies mentioned in this article, we assume that the address is the unique identifier of the Host object ). In this table, lightweight data can be used as a key, while string type names can be used as values.

The initialization method creates the table and enables it to be called by the ing function. However, it is also important to protect its independence: access from the Lua environment must not be allowed; in addition, it may still crash the Host Program in the Lua script. Using the registry for storage to ensure its independence is a method, which is a global variable that can be accessed independently by Lua API. However, because the registry is unique and global, it is used to store our ing.ObjectIt also prevents other C libraries from using it to implement other control mechanisms.

Another better solution is to provide the access type checklist interface for the binding function only. This function is not implemented until Lua5.0. In Lua5.1, there is a better and more efficient method): the use of environment tables is directly related to C functions. We set the type checklist to the Environment table bound to the function. In this way, the access to the table in the function is very efficient. Every function must be registered to Lua and inherit its environment table from the current function. Therefore, it is enough to change the environment table Association of the initialization function-and all the registered environment functions will have the same associated environment table.
 
Now, we can encode the method for checking the execution type of the binder class:

 
 
  1. Void Binder: init (const char * tname, const luaL_reg * flist ){
  2. Lua_newtable (L); // create a type Checklist
  3. Lua_replace (L, LUA_ENVIRONINDEX); // sets the table as an environment table.
  4. LuaL_register (L, tname, flist); // create a database table
  5. }
  6.  
  7. Void Binder: pushusertype (void * udata, const char * tname ){
  8. Lua_pushlightuserdata (L, udata); // push address
  9. Lua_pushvalue (L,-1); // duplicate address
  10. Lua_pushstring (L, tname); // name of the push type
  11. Lua_rawset (L, LUA_ENVIRONINDEX); // envtable [address] = type name
  12. }
  13.  
  14. Void * Binder: checkusertype (int index, const char * tname ){
  15. Void * udata = lua_touserdata (L, index );
  16. If (udata = 0 |! Checktype (udata, tname ))
  17. LuaL_typeerror (L, index, tname );
  18. Return udata;
  19. }

The surface Code uses a private method to perform type check:

 
 
  1. Int Binder: checktype (void * udata, const char * tname ){
  2. Lua_pushlightuserdata (L, udata); // push address
  3. Lua_rawget (L, LUA_ENVIRONINDEX); // obtain env [address]
  4. Const char * stored_tname = lua_tostring (t,-1 );
  5. Int result = stored_tname & strcmp (stored_tname, tname) = 0;
  6. Lua_pop (L, 1 );
  7. Return result;
  8. }

Through these practices, we make the binding policy very efficient. Similarly, the memory load is very low-allObjectThere is only one table entity. However, to prevent the expansion of the type checklist, we mustObjectTo release these tables. In the bnd_Destroy function, we must call this private method:

 
 
  1. void Binder::releaseusertype( void* udata ){  
  2.  lua_pushlightuserdata(L,udata);  
  3.  lua_pushnil(L);  
  4.  lua_settable(L,LUA_ENVIRONINDEX);  

Summary: Explain howC ++ objectBindLuaThe lightweight content has been introduced. I hope this article will help you!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.