Difference between revisions of "Script lua"

From The Foundry MODO SDK wiki
Jump to: navigation, search
(Created page with "Script_lua is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK. ==Functionality== This plugin ...")
 
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
Script_lua is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.
 
Script_lua is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.
 
 
==Functionality==
 
  
 
This plugin serves as a Lua script interpreter.
 
This plugin serves as a Lua script interpreter.
  
 
==Code Walkthrough==
 
==Code Walkthrough==
 +
 +
The code structure for this plugin is somewhat atypical. In our header we declare a variety of functions which we define in the related cpp. However, our main class, the one from which we will build our server, is in another cpp entirely and has a utility that takes the functions that we defined and adds them to the aforementioned class.
  
 
===Declarations===
 
===Declarations===
  
 +
====lxlua.h====
 +
 +
This is a struct that defines certain objects and constants that will be used by the plugin.
 +
 +
<syntaxhighlight>
 
  typedef struct st_IntrInstance {
 
  typedef struct st_IntrInstance {
 
         ILxScriptID script; // Script being executed.
 
         ILxScriptID script; // Script being executed.
Line 21: Line 25:
 
   
 
   
 
         int queryAnglesAsDegrees; // True if queried angles should be returned in degrees instead of radians.
 
         int queryAnglesAsDegrees; // True if queried angles should be returned in degrees instead of radians.
  } IntrInstance;
+
  } IntrInstance;</syntaxhighlight>
  
This is a struct that defines certain objects and constants that will be used by the plugin.
+
Declaration of functions that will be used by the plugin.
  
 +
<syntaxhighlight>
 
  extern "C"  int  l_lxout(lua_State *L);
 
  extern "C"  int  l_lxout(lua_State *L);
 
  extern "C"  int  l_lx(lua_State *L);
 
  extern "C"  int  l_lx(lua_State *L);
Line 37: Line 42:
 
  extern "C"  int  l_lxmonInit(lua_State *L);
 
  extern "C"  int  l_lxmonInit(lua_State *L);
 
  extern "C"  int  l_lxmonStep(lua_State *L);
 
  extern "C"  int  l_lxmonStep(lua_State *L);
 +
</syntaxhighlight>
  
Declaration of functions that will be used by the plugin.
+
====lxlua.cpp====
  
 +
Here we declare a variety of scripts and services that will be used below. "extern" indicates that the objects inside have already been defined elsewhere so the compiler won't complain.
 +
 +
<syntaxhighlight>
 
  extern "C" {
 
  extern "C" {
 
   
 
   
Line 48: Line 57:
 
  IntrInstance *intrCur        = NULL; /* The same as &intrStack[ intrTop ], or NULL if intrTop is -1  */
 
  IntrInstance *intrCur        = NULL; /* The same as &intrStack[ intrTop ], or NULL if intrTop is -1  */
 
   
 
   
  }
+
  }</syntaxhighlight>
 
+
Here we declare a variety of scripts and services that will be used below. "extern" indicates that the objects inside have already been defined elsewhere so the compiler won't complain.
+
  
 
===Class Declarations===
 
===Class Declarations===
  
 +
====intrlua.cpp====
 +
 +
This class is intended to take a lua script and interpret it, so we need it to be a modified text editor. To this end, we inherit from [[Scripts_(lx-scripts.hpp)#Text_Script_Interpreter|CLxImpl_TextScriptInterpreter]].
 +
 +
The first two functions of note inside the class are those with the tsi prefix, indicating that they are redeclarations of virtual functions inside the CLxImpl_TextScriptInterpreter class.The ValidateFileType makes sure that the file we are being passed is of the correct format. The Run function takes the file we have been given and interprets it line by line.
 +
 +
The Init starts up our interpreter. However, on top of that it also adds all the functions that we declared in the header file and then defined in a separate cpp. These functions handle logging, options, and the progress bar. There is also a sort of master function which performs all the queries or executes. The Kill function simply shuts down our interpreter.
 +
 +
<syntaxhighlight>
 
  class CLuaInterpreter : public CLxImpl_TextScriptInterpreter
 
  class CLuaInterpreter : public CLxImpl_TextScriptInterpreter
 
  {
 
  {
Line 79: Line 95:
 
     private:
 
     private:
 
         lua_State *L;
 
         lua_State *L;
  };
+
  };</syntaxhighlight>
 
+
This class is intended to take a lua script and interpret it, so we need it to be a modified text editor. To this end, we inherit from [[Scripts_(lx-scripts.hpp)#Text_Script_Interpreter|CLxImpl_TextScriptInterpreter]].
+
  
 
===[[Server_Tags|Server Tags]]===
 
===[[Server_Tags|Server Tags]]===
 +
 +
Servers tags are examined when the server is initialized, and give information about the server. We set the tags in this case by taking descinfo[] arrays and associating the relevant data with the corresponding flags.
 +
 +
The tags here indicate that the server we will be exporting has the name "Lua Scripts" and deals with files of the type .lua.
 +
 +
<syntaxhighlight>
 +
LXtTagInfoDesc CLuaInterpreter::descInfo[] = {
 +
        { LXsSRV_USERNAME, "Lua Scripts" },
 +
        { LXsLOD_DOSPATTERN, "*.lua" },
 +
        { 0 }
 +
};</syntaxhighlight>
  
 
The tags here indicate that CLuaInterpreter has the name Lua Scripts and takes files with the .lua extension.
 
The tags here indicate that CLuaInterpreter has the name Lua Scripts and takes files with the .lua extension.
Line 89: Line 114:
 
===[[Initialize_(index)|Initialize]]===
 
===[[Initialize_(index)|Initialize]]===
  
 +
Intialize is called when we add the plugin to modo, and is the utility that exports the server. The LXx_ADD_SERVER method is simply a wrapper that is identical to normal method of adding a server, with the arguments being (interface_to_be_added, class_you_depend_on, server_name).
 +
 +
This initialize function exports one server of the type TextScriptInterpreter that is dependent on the class CLuaInterpreter and has the name lua.
 +
 +
<syntaxhighlight>
 
         void
 
         void
 
  initialize ()
 
  initialize ()
 
  {
 
  {
 
         LXx_ADD_SERVER (TextScriptInterpreter, CLuaInterpreter, "lua");
 
         LXx_ADD_SERVER (TextScriptInterpreter, CLuaInterpreter, "lua");
  }  
+
  } </syntaxhighlight>
 
+
This initialize function exports one server of the type TextScriptInterpreter that is dependent on the class CLuaInterpreter and has the name lua.
+
  
 
===Implementations===
 
===Implementations===
  
 +
====lxlua.cpp====
 +
 +
Add a string to a parent log, possibly returning a log entry for sub-entries
 +
 +
<syntaxhighlight>
 
         static void  
 
         static void  
 
  AddLogStringEntry (
 
  AddLogStringEntry (
Line 107: Line 140:
 
         CLxLoc_LogEntry &entry)
 
         CLxLoc_LogEntry &entry)
 
  {
 
  {
         if (logSrv->NewEntry (code, s, entry)) {
+
         ...
                parent.AddEntry (entry);
+
        }
+
 
  }
 
  }
 +
</syntaxhighlight>
  
Add a string to a parent log, possibly returning a log entry for sub-entries
+
Adding a log entry to a parent, for clients with no need for the entry.
  
 +
<syntaxhighlight>
 
  void
 
  void
 
  AddLogString (
 
  AddLogString (
Line 121: Line 154:
 
         int persist)
 
         int persist)
 
  {  
 
  {  
         CLxLoc_LogEntry entry;
+
         ...
+
  }</syntaxhighlight>
        AddLogStringEntry (parent, s, code, persist, entry);
+
  }
+
  
Adding a log entry to a parent, for clients with no need for the entry.
+
Logging for clients with no need for the entry.
  
 +
<syntaxhighlight>
 
         void
 
         void
 
  LogString (
 
  LogString (
Line 134: Line 166:
 
         int persist)
 
         int persist)
 
  {
 
  {
         CLxLoc_LogEntry entry;
+
         ...
+
  }</syntaxhighlight>
        LogStringEntry (s, code, persist, entry);
+
  }
+
  
Logging for clients with no need for the entry.
+
lxout:  Output a string to the log
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxout(lua_State *L)
 
  l_lxout(lua_State *L)
 
  {
 
  {
         const char *c = lua_tostring(L, 1);
+
         ...
         
+
  }</syntaxhighlight>
        if(c)
+
                LogString( c, LXe_INFO, 0 );
+
        else
+
                LogString( "nil", LXe_INFO, 0 );
+
+
        return 0;
+
  }
+
  
lxout:  Output a string to the log
+
Internal function to perform queries and executions
  
 +
<syntaxhighlight>
 
  #define LXEVALMODEf_QUERY 1 // Query only; fail if no query is marked
 
  #define LXEVALMODEf_QUERY 1 // Query only; fail if no query is marked
 
  #define LXEVALMODEf_EXECUTE 2 // Execute only; fail if a query is marked
 
  #define LXEVALMODEf_EXECUTE 2 // Execute only; fail if a query is marked
Line 163: Line 188:
 
  lxeval(lua_State *L, int mode)
 
  lxeval(lua_State *L, int mode)
 
  {
 
  {
         CLxUser_Command command;
+
         ...
        CLxLoc_LogEntry entry;
+
  }</syntaxhighlight>
        int i, x, type, queryArgIndex;
+
        int iVal;
+
        double fVal, fScalar = 1.0;
+
        char sVal[2048];
+
        const char *typeName;
+
        LxResult rc;
+
        const LXtTextValueHint *hints    = NULL;
+
        unsigned execFlags = intrCur->execFlags;
+
        const char *c        = lua_tostring(L, 1);
+
        bool isOK;
+
+
        OutputTraceInfo( c, entry );
+
+
        /* Spawn the command and get the query argument */
+
        isOK = cmdSrv->NewCommandFromString (command, c, execFlags, queryArgIndex);
+
        if( !isOK ) {
+
                intrCur->rc = LXe_FAILED;
+
+
                if( intrCur->tracing )
+
                        AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxq/lx/lxeval failed: error spawning command/parsing arguments\03(f:FONT_DEFAULT)", LXe_FAILED, 0 );
+
+
                return LXe_FAILED;
+
        }
+
+
if( (mode == LXEVALMODEf_EXECUTE) || // Execute only flag; execute if there's a query or not
+
            ((queryArgIndex == -1) && (mode & LXEVALMODEf_EXECUTE)) ) { // No query arg; execute if the execute flag is set (in case of "either" mode)
+
                /* Execute */
+
                if( !(mode & LXEVALMODEf_EXECUTE) ) {
+
                        if( intrCur->tracing )
+
                                AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lx failed: unprocessed query argument (i.e., \'?\') specified in command  string\03(f:FONT_DEFAULT)",   
+
  LXe_FAILED, 0 );
+
+
                        return LXe_FAILED;
+
                }
+
+
                intrCur->rc = cmdSrv->ExecuteSpecial( execFlags, command, queryArgIndex );
+
                lua_pushnumber(L, LXx_OK( intrCur->rc ) ? 1 : 0 );
+
+
                return LXe_OK;
+
        }
+
+
        /* See if we're allowed to query */
+
        if( !(mode & LXEVALMODEf_QUERY) ) {
+
                if( intrCur->tracing )
+
                        AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxq/lxeval failed: no query (i.e., \'?\') marked in command string\03(f:FONT_DEFAULT)", LXe_FAILED, 0 );
+
+
                return LXe_CMD_NO_QUERY_MARKED;
+
        }
+
+
        /* Create the value array and query the command */
+
        CLxUser_ValueArray qval;
+
+
        CLxUser_Attributes attr;
+
        if (attr.set(command)) {
+
                /* Get the type */
+
                type = attr.Type (queryArgIndex);
+
                if (type != -1) {
+
                        /* Get the type name */
+
                        rc = attr.TypeName (queryArgIndex, &typeName);
+
+
                        if (LXx_OK (rc)) {
+
                                /* Create a value array */
+
                                ILxUnknownID oqval;
+
                                rc = cmdSrv->CreateQueryObject( typeName, (void **)&oqval );
+
                                if (LXx_OK (rc)) {
+
                                        /* Query */
+
                                        qval.set (oqval);
+
                                        if( qval.set (oqval) )
+
                                                rc = command.Query (queryArgIndex, qval);
+
                                }
+
                        }
+
                }
+
                else {
+
                        rc = LXe_FAILED;
+
                }
+
        }
+
+
        if( LXx_FAIL (rc) ) {
+
                if( intrCur->tracing )
+
                        AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxq/lxeval failed: error querying command\03(f:FONT_DEFAULT)", LXe_FAILED, 0 );
+
+
                return rc;
+
        }
+
+
        /* Return the values in the value array to the script */
+
        i = qval.Count();
+
+
        if(0 == i) {
+
                /* Nothing is on the stack; return nil */
+
                lua_pushnil( L );
+
                return LXe_OK;
+
        }
+
+
        if( attr != NULL )
+
                hints = attr.Hints( queryArgIndex );
+
+
        lua_newtable(L);
+
+
        if( intrCur->queryAnglesAsDegrees ) {
+
                /* Special case:  convert angles to degrees, if applicable */
+
                if( strcmp( typeName, "angle" ) == 0 )
+
                        fScalar = 57.295780490442968321226628812406; // 180 / 3.1415926;
+
        }
+
+
        for(x = 0 ; x < i ; x++)
+
        {
+
                lua_pushnumber(L, x+1); // The key, which is just the index of the value
+
+
                if(type == LXi_TYPE_INTEGER)
+
                {
+
                        bool asNumber = 1;
+
+
                        qval.GetInt (x, &iVal);
+
                        if( hints != NULL ) {
+
                                LxResult encodeResult = valueSrv->TextHintEncode( iVal, hints, sVal, 2048 );
+
                                if( encodeResult != LXe_OK_NO_CHOICES ) {
+
                                        lua_pushlstring(L, sVal, strlen(sVal));
+
                                        asNumber = 0;
+
+
                                        if( intrCur->tracing ) {
+
                                                char buf[ 64 ];
+
                                                sprintf( buf, "\03(f:FONT_ITALIC)lxtrace, lxq, %d:\03(f:FONT_DEFAULT)  %s", x+1, sVal );
+
                                                AddLogString( entry, buf, LXe_INFO, 0 );
+
                                        }
+
                                }
+
                        }
+
+
                        if( asNumber ) {
+
                                lua_pushnumber(L, iVal);
+
+
                                if( intrCur->tracing ) {
+
                                        char buf[ 64 ];
+
                                        sprintf( buf, "\03(f:FONT_ITALIC)lxtrace, lxq, %d:\03(f:FONT_DEFAULT)  %d", x+1, iVal );
+
                                        AddLogString( entry, buf, LXe_INFO, 0 );
+
                                }
+
                        }
+
                }
+
                else if(type == LXi_TYPE_FLOAT)
+
                {
+
                        qval.GetFloat (x, &fVal);
+
                        fVal *= fScalar;
+
                        lua_pushnumber(L, fVal);
+
 
+
                        if( intrCur->tracing ) {
+
                                char buf[ 64 ];
+
                                sprintf( buf, "\03(f:FONT_ITALIC)lxtrace, lxq, %d:\03(f:FONT_DEFAULT)  %g", x+1, fVal );
+
                                AddLogString( entry, buf, LXe_INFO, 0 );
+
                        }
+
                }
+
                else if(type == LXi_TYPE_STRING)
+
                {
+
                        qval.GetString (x, sVal, 2048);
+
                        lua_pushlstring(L, sVal, strlen(sVal));
+
+
                        if( intrCur->tracing ) {
+
                                char buf[ 2048 ];
+
                                sprintf( buf, "\03(f:FONT_ITALIC)lxtrace, lxq, %d:\03(f:FONT_DEFAULT)  %s", x+1, sVal );
+
                                AddLogString( entry, buf, LXe_INFO, 0 );
+
                        }
+
                }
+
                else
+
                {
+
                        /* Object; convert to a string and return */
+
                        char buffer[2048];
+
+
                        LXtObjectID value;
+
                        if( LXx_FAIL( (qval.GetValue (x, (void **) &value) ))) {
+
                                lua_pushnil( L );
+
                                continue;
+
                        }
+
+
                        /*
+
                        * Check if we have a string converter.
+
                        */
+
                        CLxUser_StringConversion stringer;
+
                        stringer.set (value);
+
                        if (!stringer.test ()) {
+
                                lua_pushnil( L );
+
                                continue;
+
                        }
+
+
                        if( LXx_FAIL( stringer.Encode ( buffer, 2048 ) ) ) {
+
                                lua_pushnil( L );
+
                                continue;
+
                        }
+
+
                        lua_pushlstring(L, buffer, strlen(buffer));
+
+
                        if( intrCur->tracing ) {
+
                                char buf[ 2100 ];
+
                                sprintf( buf, "\03(f:FONT_ITALIC)lxtrace, lxq, %d:\03(f:FONT_DEFAULT)  %s", x+1, buffer );
+
                                AddLogString( entry, buf, LXe_INFO, 0 );
+
                        }
+
                }
+
+
                lua_settable(L, -3);
+
        }
+
+
        return 1;
+
}
+
  
Internal function to perform queries and executions
+
At some point in the future, lxq() needs to use the raw string conversion feature to ensure that the values that are queried are the same kinds of values that get passed to commands.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxq(lua_State *L)
 
  l_lxq(lua_State *L)
 
  {
 
  {
         if( LXx_FAIL( lxeval( L, LXEVALMODEf_QUERY ) ) )
+
         ...
                return 0;
+
  }</syntaxhighlight>
+
        return 1;
+
  }
+
  
At some point in the future, lxq() needs to use the raw string conversion feature to ensure that the values that are queried are the same kinds of values that get passed to commands.
+
lxqt(): Query a toggle command's toggle argument.  Returns the query state on success (1 or 0), or 0 on failure.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxqt(lua_State *L)
 
  l_lxqt(lua_State *L)
 
  {
 
  {
         CLxUser_Command command;
+
         ...
        CLxLoc_LogEntry entry;
+
  }</syntaxhighlight>
        int state;
+
        unsigned execFlags = intrCur->execFlags;
+
        const char *c        = lua_tostring(L, 1);
+
        bool isOK;
+
   
+
        OutputTraceInfo( c, entry );
+
+
        /* Spawn the command */
+
        int queryArgIndex;
+
        isOK = cmdSrv->NewCommandFromString( command, c, execFlags, queryArgIndex );
+
        if( !isOK ) {
+
                intrCur->rc = LXe_FAILED;
+
+
                if( intrCur->tracing )
+
                        AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxqt failed: error spawning command/parsing arguments\03(f:FONT_DEFAULT)", LXe_FAILED, 0 );
+
+
                lua_pushnumber( L, 0 );
+
                return 1;
+
        }
+
+
        /* Query the toggle argument */
+
        intrCur->rc = cmdSrv->GetToggleArgState( command, &state, NULL );
+
+
        if( intrCur->tracing ) {
+
                if( LXx_OK( intrCur->rc ) ) {
+
                        if( state ) {
+
                                AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxqt, toggled state is true (1)\03(f:FONT_DEFAULT)", LXe_INFO, 0 );
+
                                lua_pushnumber( L, 1 );
+
                        } else {
+
                                AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxqt, toggled state is false (0)\03(f:FONT_DEFAULT)", LXe_INFO, 0 );
+
                                lua_pushnumber( L, 0 );
+
                        }
+
                } else {
+
                        AddLogString( entry, "\03(f:FONT_ITALIC)lxtrace, lxqt failed: error querying the command\03(f:FONT_DEFAULT)", LXe_FAILED, 0 );
+
                        lua_pushnumber( L, 0 );
+
                }
+
        }
+
+
        return 1;
+
}
+
  
lxqt(): Query a toggle command's toggle argument.  Returns the query state on success (1 or 0), or 0 on failure.
+
Return true if the last command executed/queried successfully.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxok(lua_State *L)
 
  l_lxok(lua_State *L)
 
  {
 
  {
         lua_pushboolean(L, LXx_OK( intrCur->rc ) ? 1 : 0);
+
         ...
        return 1;
+
 
  }
 
  }
 +
</syntaxhighlight>
  
Return true if the last command executed/queried successfully.
+
Return the LxResult code of the last command executed or quieried.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxres(lua_State *L)
 
  l_lxres(lua_State *L)
 
  {
 
  {
        lua_pushnumber(L, intrCur->rc);
+
      ...
        return 1;
+
  }</syntaxhighlight>
  }
+
  
Return the LxResult code of the last command executed or quieried.
+
When activated, lx() and lxq() data is output to the event log.  If no args are passed in, this returns if tracing is active or not.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxtrace(lua_State *L)
 
  l_lxtrace(lua_State *L)
 
  {  
 
  {  
         /* See if there's at least one argument */
+
         ...
        const char *c = lua_tostring(L, 1);
+
  }</syntaxhighlight>
   
+
        if( c != NULL ) {
+
                intrCur->tracing = atoi( c ); // For some reason, lua_toboolean() is always returning 1, so we'll just do this
+
                if( intrCur->tracing )
+
                        LogString( "\03(f:FONT_ITALIC)lxtrace: Enabled\03(f:FONT_DEFAULT)", LXe_INFO, 0 );
+
                else
+
                        LogString( "\03(f:FONT_ITALIC)lxtrace: Disabled\03(f:FONT_DEFAULT)", LXe_INFO, 0 );
+
        }
+
+
        lua_pushnumber(L, intrCur->tracing);
+
        return 1;
+
}
+
  
When activated, lx() and lxq() data is output to the event log.  If no args are passed in, this returns if tracing is active or not.
+
Get the state of an option.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxoption(lua_State *L)
 
  l_lxoption(lua_State *L)
 
  {
 
  {
        const char *tag = lua_tostring(L, 1);
+
      ...
+
  }</syntaxhighlight>
        if( strcmp( tag, "queryAnglesAs" ) == 0 ) {
+
                /* Query Angles As. Returns "degrees" or "radians" */
+
                const char *state = intrCur->queryAnglesAsDegrees ? "degrees" : "radians";
+
                lua_pushlstring(L, state, strlen(state));
+
   
+
                return 1;
+
        }
+
+
        /* Unknown tag */
+
        return 0;
+
}
+
  
 
Get the state of an option.
 
Get the state of an option.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxsetOption(lua_State *L)
 
  l_lxsetOption(lua_State *L)
 
  {
 
  {
         const char *tag  = lua_tostring(L, 1);
+
         ...
        const char *value = lua_tostring(L, 2);
+
  }</syntaxhighlight>
+
        if( strcmp( tag, "queryAnglesAs" ) == 0 ) {
+
                /* Query Angles As. Returns "degrees" or "radians" */
+
                if( strcmp( tag, "queryAnglesAs" ) == 0 ) {
+
                        /* Query Angles As. Supports "degrees" or "radians" */
+
                        if( strcmp( value, "degrees" ) == 0 ) {
+
                                intrCur->queryAnglesAsDegrees = 1;
+
   
+
                        } else if( strcmp( value, "radians" ) == 0 ) {
+
                                intrCur->queryAnglesAsDegrees = 0;
+
+
                        } else {
+
                                lua_pushboolean(L, 0);
+
                                return 0;
+
                        }
+
+
                        lua_pushboolean(L, 1);
+
                }
+
+
                return 1;
+
        }
+
+
        /* Unknown tag */
+
        lua_pushboolean(L, 0);
+
        return 0;
+
}
+
  
Get the state of an option.
+
Initialize the progress bar. Initialize the progress bar with n total steps.  Returns 0 on error.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxmonInit(lua_State *L)
 
  l_lxmonInit(lua_State *L)
 
  {
 
  {
        int count;
+
      ...
        LxResult rc;
+
  } </syntaxhighlight>
+
        /* See if there's at least one argument */
+
        if( lua_tostring(L, 1) == NULL ) {
+
                lua_pushboolean(L, 0);
+
                return 1;
+
        }
+
+
        /* Make sure there's at least one step */
+
        count = (int)lua_tonumber(L, 1);
+
        if( count < 1 ) {
+
                lua_pushboolean(L, 0);
+
                return 1;
+
        }
+
+
        /* Get the monitor */
+
        if( intrCur->monitor == NULL ) {
+
                if( intrCur->askedForMonitor ) {
+
                        lua_pushboolean(L, 0);
+
                        return 1;
+
                }
+
+
                if( LXx_FAIL( dlgSrv[0]->MonitorAllocate( dlgSrv, "Progress", (void **)&intrCur->monitor ) ) || (intrCur->monitor == NULL) ) {
+
                        lua_pushboolean(L, 0);
+
                        return 1;
+
                }
+
+
                intrCur->askedForMonitor = 1;
+
        }
+
+
        /* Initialize the monitor */
+
        rc = intrCur->monitor[0]->Initialize( intrCur->monitor, count );
+
        if( LXx_OK( rc ) )
+
                lua_pushboolean(L, 1);
+
        else
+
                lua_pushboolean(L, 0);
+
+
        return 1;
+
}
+
  
Initialize the progress bar. Initialize the progress bar with n total steps.  Returns 0 on error.
+
Step the progress bar. Increment the progress bar by n steps, or 1 step if no steps are providedThis always returns true unless the user aborts, in which case it returns false.
  
 +
<syntaxhighlight>
 
         int
 
         int
 
  l_lxmonStep(lua_State *L)
 
  l_lxmonStep(lua_State *L)
 
  {
 
  {
        int steps;
+
      ...
        LxResult rc;
+
  }</syntaxhighlight>
+
        if( intrCur->monitor == NULL ) {
+
                lua_pushboolean(L, 1);
+
                return 1;
+
        }
+
+
        /* See if there are any arguments */
+
        if( lua_tostring(L, 1) == NULL )
+
                steps = 1;
+
        else
+
                steps = (int)lua_tonumber(L, 1);
+
+
        /* Make sure there's at least one step */
+
        if( steps < 1 ) {
+
                lua_pushboolean(L, 1);
+
                return 1;
+
        }
+
+
        /* Increment the monitor and check for a user abort */
+
        rc = intrCur->monitor[0]->Increment( intrCur->monitor, steps );
+
        if( LXx_OK( rc ) )
+
                lua_pushboolean(L, 1);
+
        else
+
                lua_pushboolean(L, 0);
+
+
        return 1;
+
}
+
  
Step the progress bar. Increment the progress bar by n steps, or 1 step if no steps are provided.  This always returns true unless the user aborts, in which case it returns false.
+
verifies that the file passed in is of the lua type.
  
 +
<syntaxhighlight>
 
         LxResult
 
         LxResult
 
  CLuaInterpreter::tsi_ValidateFileType (
 
  CLuaInterpreter::tsi_ValidateFileType (
Line 604: Line 281:
 
         const char *firstLine)
 
         const char *firstLine)
 
  {
 
  {
         if (!strstr (firstLine, "lua"))
+
         ...
                return LXe_SCRIPT_UNKNOWN;
+
  }</syntaxhighlight>
         
+
        return LXe_OK;
+
+
  }
+
  
verifies that the file passed in is of the lua type.
+
====intrlua.cpp====
 +
 
 +
adds all of the functions just implemented in lxlua.cpp to CLuaInterpreter
  
 +
<syntaxhighlight>
 
         void
 
         void
 
  CLuaInterpreter::Init()
 
  CLuaInterpreter::Init()
 
  {
 
  {
         logSrv = &logSvc;
+
         ...
        cmdSrv = &cmdSvc;
+
  }</syntaxhighlight>
        valueSrv = &valueSvc;
+
+
        L = lua_open();
+
       
+
        /* Add the new functions to lua */
+
        lua_pushcfunction(L, l_lxout);
+
        lua_setglobal(L, "lxout");
+
       
+
        lua_pushcfunction(L, l_lx);
+
        lua_setglobal(L, "lx");
+
       
+
        lua_pushcfunction(L, l_lxq);
+
        lua_setglobal(L, "lxq");
+
+
        lua_pushcfunction(L, l_lxqt);
+
        lua_setglobal(L, "lxqt");
+
+
        lua_pushcfunction(L, l_lxeval);
+
        lua_setglobal(L, "lxeval");
+
+
        lua_pushcfunction(L, l_lxok);
+
        lua_setglobal(L, "lxok");
+
+
        lua_pushcfunction(L, l_lxres);
+
        lua_setglobal(L, "lxres");
+
+
        lua_pushcfunction(L, l_lxtrace);
+
        lua_setglobal(L, "lxtrace");
+
+
        lua_pushcfunction(L, l_lxoption);
+
        lua_setglobal(L, "lxoption");
+
+
        lua_pushcfunction(L, l_lxsetOption);
+
        lua_setglobal(L, "lxsetOption");
+
+
        lua_pushcfunction(L, l_lxmonInit);
+
        lua_setglobal(L, "lxmonInit");
+
       
+
        lua_pushcfunction(L, l_lxmonStep);
+
        lua_setglobal(L, "lxmonStep");
+
 
+
        /* Other lua init */
+
+
        /*
+
        * Updated for Lua 5.1, per the Lua API Programming FAQ:
+
        *
+
        * http://lua-users.org/wiki/LuaFaq
+
        *
+
        * See "Why do I get a 'no calling environment' error?"
+
        */
+
        luaL_openlibs(L);
+
   
+
        /* Init our level of the stack */
+
        intrTop++;
+
        assert( intrTop < MAX_STACK );
+
+
        intrCur = &intrStack[ intrTop ];
+
        intrCur->script              = NULL;
+
        intrCur->monitor              = NULL;
+
        intrCur->askedForMonitor      = 0;
+
        intrCur->rc                  = LXe_OK;
+
        intrCur->tracing              = 0;
+
        intrCur->execFlags            = LXfCMD_EXEC_DEFAULT;
+
        intrCur->queryAnglesAsDegrees = 1;
+
}
+
  
adds all of the functions just implemented in lxlua.cpp to CLuaInterpreter
+
performs most of the work, is the function that actually interprets the file.
  
 +
<syntaxhighlight>
 
         LxResult
 
         LxResult
 
  CLuaInterpreter::tsi_Run (
 
  CLuaInterpreter::tsi_Run (
Line 693: Line 305:
 
         ILxUnknownID messObj )
 
         ILxUnknownID messObj )
 
  {
 
  {
         // Hybrid access: Let the localization handle the reference-counting, but
+
         ...
        // extract the interface for old-style direct usage.
+
  }</syntaxhighlight>
        //
+
        CLxLoc_Script u_script (scriptObj);
+
        CLxLoc_Message u_mess  (messObj);
+
        ILxScriptID script = u_script.m_loc;
+
        ILxMessageID mess  = u_mess.m_loc;
+
   
+
        const char *name, *hash;
+
        unsigned int size = 0;
+
        const char *st;
+
        char *localBuf, nameBuf[ 512 ];
+
        LxResult rc = LXe_OK;
+
        int error;
+
        int errMsg = -1;
+
        int numArgs = 0;
+
+
        Init();
+
        script[0]->GetBuffer( script, &st, &size );
+
        size -= 1; // Remove the \0 from the buffer length
+
+
        /* Variable initialization */
+
        scriptSrv          = (ILxScriptSysServiceID)lx::GetGlobal(LXu_SCRIPTSYSSERVICE);
+
        dlgSrv            = (ILxStdDialogServiceID)lx::GetGlobal (LXu_STDDIALOGSERVICE);
+
        intrCur->script    = script;
+
        intrCur->execFlags = execFlags;
+
+
        /* Load up the default libraries */
+
+
        /*
+
        * Updated for Lua 5.1, per the Lua API Programming FAQ:
+
        *
+
        * http://lua-users.org/wiki/LuaFaq
+
        *
+
        * See "Why do I get a 'no calling environment' error?"
+
        */
+
        luaL_openlibs(L);
+
+
        /* Log Setup */
+
        if( LXx_FAIL( script[0]->UserName( script, &name ) ) )
+
                script[0]->Hash( script, &name );
+
+
        sprintf( nameBuf, "%s  (lua script)", name );
+
        logSvc.NewEntry( LXe_INFO, nameBuf, intrCur->log );
+
+
        if( intrTop == 0 ) {
+
                /* Add the entry to the subsystem */
+
                CLxLoc_Log logSubSys;
+
                logSvc.GetSubSystem( LXsLOG_SCRIPTSYS, logSubSys );
+
+
                logSubSys.AddEntry( ILxUnknownID(intrCur->log) );
+
+
        } else {
+
                /* Add the entry to the parent script's entry */
+
                intrStack[ intrTop - 1 ].log.AddEntry (intrCur->log);
+
        }
+
+
        /* Load the script into the interpreter */
+
        error = luaL_loadbuffer(L, st, strlen(st), "lua script");
+
        if( error ) {
+
                errMsg = 1;
+
        } else {
+
                /* Create the argument table */
+
                lua_newtable(L); // arg table
+
+
                /* Push the script name onto the table at index 0 */
+
                script[0]->Hash( script, &hash );
+
+
                lua_pushnumber(L, 0);
+
                lua_pushstring(L, hash);
+
                lua_settable(L, -3);
+
+
                /* Parse the arguments and add them to the table */
+
                if (args) {
+
                        int alen = static_cast<int>(strlen (args)) + 1;
+
                        localBuf = new char [alen];
+
                        strcpy (localBuf, args);
+
+
                        char *arg = localBuf;
+
                        char *endQuote;
+
+
                        int index = 0;
+
+
                        while( (arg != NULL) && (arg[0] != '\0') ) {
+
                                /* Skip white space */
+
                                for( /*NULL*/; isspace( arg[0] ) && (arg[0] != '\0'); arg++ ) { ; }
+
+
                                if( arg[0] == '\0' )
+
                                        break;
+
+
                                /* Check for quoted arguments */
+
                                if( arg[0] == '\"' ) {
+
                                        arg++;
+
                                        endQuote = strchr( arg, '\"' );
+
                                        if( endQuote == NULL ) {
+
                                                /* Unbalanced quotes; fail */
+
                                                rc = LXe_FAILED;
+
+
                                                mess[0]->SetCode          (mess, rc);
+
                                                mess[0]->SetMessage        (mess, "lualintr", NULL, 3);
+
                                                mess[0]->SetArgumentString (mess, 1, args);
+
+
                                                error  = 1;
+
                                                errMsg = 2;
+
                                                break;
+
                                        }
+
+
                                        endQuote[0] = '\0';
+
+
                                        lua_pushnumber(L, ++index);
+
                                        lua_pushstring(L, arg);
+
                                        lua_settable(L, -3);
+
+
                                        numArgs++;
+
+
                                        arg = endQuote + 1;
+
+
                                } else {
+
                                        /* Normal space delimiting */
+
                                        endQuote = strchr( arg, ' ' );
+
                                        if( endQuote )
+
                                                endQuote[0] = '\0';
+
+
                                        lua_pushnumber(L, ++index);
+
                                        lua_pushstring(L, arg);
+
                                        lua_settable(L, -3);
+
                                        numArgs++;
+
+
                                        if( endQuote == NULL )
+
                                                arg = NULL;
+
                                        else
+
                                                arg = endQuote + 1;
+
                                }
+
                        }
+
                }
+
+
                /* Make the table into the global "arg" variable */
+
                lua_setglobal(L, "arg");
+
+
                if( !error ) {
+
                        /* Execute */
+
                        error = lua_pcall(L, 0, 0, 0);
+
                        if( error )
+
                                errMsg = 2;
+
                }
+
        }
+
+
        if( (errMsg == 1) || (errMsg == 2) ) {
+
                /* Error Handling */
+
                const char *c = lua_tostring(L, -1);
+
+
                LogString( c, LXe_FAILED, 0 );
+
                lua_pop(L, 1);
+
+
                rc = LXe_FAILED;
+
+
                // TODO:  Figure out how to handle aborts differently from failures
+
+
                mess[0]->SetCode(          mess, rc );
+
                mess[0]->SetMessage(        mess, "luaintr", NULL, errMsg );
+
                mess[0]->SetArgumentString( mess, 1, c );
+
        }
+
+
        /* Clean Up */
+
        if (args)
+
                delete[] localBuf;
+
+
        Kill();
+
+
        return rc;
+
+
  
performs most of the work, is the function that actually interprets the file.
+
This function cleans up.
  
 +
<syntaxhighlight>
 
         void
 
         void
 
  CLuaInterpreter::Kill()
 
  CLuaInterpreter::Kill()
 
  {
 
  {
         lua_close(L);
+
         ...
 
   
 
   
        /* Clean up our level of the stack */
+
  }</syntaxhighlight>
        intrCur->script    = NULL;
+
        intrCur->rc        = LXe_OK;
+
        intrCur->tracing  = 0;
+
        intrCur->execFlags = LXfCMD_EXEC_DEFAULT;
+
   
+
        if( intrCur->askedForMonitor ) {
+
                dlgSrv[0]->MonitorRelease( dlgSrv );
+
+
                intrCur->askedForMonitor = 0;
+
                intrCur->monitor        = NULL;
+
        }
+
+
        assert( intrTop >= -1 );
+
+
        /* Set the current pointer to the previous level */
+
        intrTop--;
+
        intrCur = (intrTop == -1) ? NULL : &intrStack[ intrTop ];
+
+
}
+
  
This function cleans up.
+
[[Category: SDK Examples]]

Latest revision as of 19:00, 16 September 2013

Script_lua is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

This plugin serves as a Lua script interpreter.

Code Walkthrough

The code structure for this plugin is somewhat atypical. In our header we declare a variety of functions which we define in the related cpp. However, our main class, the one from which we will build our server, is in another cpp entirely and has a utility that takes the functions that we defined and adds them to the aforementioned class.

Declarations

lxlua.h

This is a struct that defines certain objects and constants that will be used by the plugin.

 typedef struct st_IntrInstance {
        ILxScriptID	 script;		// Script being executed.
        ILxMonitorID	 monitor;		// Progress monitor used by lxmon??? functions.
        int		 askedForMonitor;	// True if we asked for the monitor.
        LxResult	 rc;			// Result code from the most recently executed/queried command.
        int		 tracing;		// 1 if tracing is active, 0 if inactive.
        int		 execFlags;		// Flags provided by the caller of Run() and used for command execution.
 
        CLxUser_LogEntry log;			// For logging data
 
        int		 queryAnglesAsDegrees;	// True if queried angles should be returned in degrees instead of radians.
 } IntrInstance;

Declaration of functions that will be used by the plugin.

 extern "C"  int   l_lxout(lua_State *L);
 extern "C"  int   l_lx(lua_State *L);
 extern "C"  int   l_lxq(lua_State *L);
 extern "C"  int   l_lxqt(lua_State *L);
 extern "C"  int   l_lxeval(lua_State *L);
 extern "C"  int   l_lxok(lua_State *L);
 extern "C"  int   l_lxres(lua_State *L);
 extern "C"  int   l_lxtrace(lua_State *L);
 extern "C"  int   l_lxoption(lua_State *L);
 extern "C"  int   l_lxsetOption(lua_State *L);
 extern "C"  int   l_lxmonInit(lua_State *L);
 extern "C"  int   l_lxmonStep(lua_State *L);

lxlua.cpp

Here we declare a variety of scripts and services that will be used below. "extern" indicates that the objects inside have already been defined elsewhere so the compiler won't complain.

 extern "C" {
 
 ILxScriptSysServiceID		 scriptSrv;			/* Script system service; common to all instances                */
 ILxStdDialogServiceID		 dlgSrv;			/* Standard dialog service; common to all instances              */
 IntrInstance			 intrStack[ MAX_STACK ];	/* Interpreter stack; allows scripts to execute other scripts    */
 int				 intrTop        = -1;		/* The current script being executed as an index into interStack */
 IntrInstance			*intrCur        = NULL;		/* The same as &intrStack[ intrTop ], or NULL if intrTop is -1   */
 
 }

Class Declarations

intrlua.cpp

This class is intended to take a lua script and interpret it, so we need it to be a modified text editor. To this end, we inherit from CLxImpl_TextScriptInterpreter.

The first two functions of note inside the class are those with the tsi prefix, indicating that they are redeclarations of virtual functions inside the CLxImpl_TextScriptInterpreter class.The ValidateFileType makes sure that the file we are being passed is of the correct format. The Run function takes the file we have been given and interprets it line by line.

The Init starts up our interpreter. However, on top of that it also adds all the functions that we declared in the header file and then defined in a separate cpp. These functions handle logging, options, and the progress bar. There is also a sort of master function which performs all the queries or executes. The Kill function simply shuts down our interpreter.

 class CLuaInterpreter : public CLxImpl_TextScriptInterpreter
 {
        CLxUser_LogService	logSvc;
        CLxUser_CommandService	cmdSvc;
        CLxUser_ValueService	valueSvc;
 
    public:
        virtual			~CLuaInterpreter () {}
 
        virtual LxResult	 tsi_ValidateFileType (
                                        ILxUnknownID	 scriptObj,
                                        const char	*firstLine) LXx_OVERRIDE;
        virtual LxResult	 tsi_Run (
                                        ILxUnknownID	 scriptObj,
                                        int		 execFlags,
                                        const char	*args,
                                        ILxUnknownID	 messObj) LXx_OVERRIDE;
 
        void			 Init ();
        void			 Kill ();
 
        static LXtTagInfoDesc	 descInfo[];
 
    private:
        lua_State		*L;
 };

Server Tags

Servers tags are examined when the server is initialized, and give information about the server. We set the tags in this case by taking descinfo[] arrays and associating the relevant data with the corresponding flags.

The tags here indicate that the server we will be exporting has the name "Lua Scripts" and deals with files of the type .lua.

 LXtTagInfoDesc	 CLuaInterpreter::descInfo[] = {
        { LXsSRV_USERNAME,	"Lua Scripts"	},
        { LXsLOD_DOSPATTERN,	"*.lua"		},
        { 0 }
 };

The tags here indicate that CLuaInterpreter has the name Lua Scripts and takes files with the .lua extension.

Initialize

Intialize is called when we add the plugin to modo, and is the utility that exports the server. The LXx_ADD_SERVER method is simply a wrapper that is identical to normal method of adding a server, with the arguments being (interface_to_be_added, class_you_depend_on, server_name).

This initialize function exports one server of the type TextScriptInterpreter that is dependent on the class CLuaInterpreter and has the name lua.

        void
 initialize ()
 {
        LXx_ADD_SERVER (TextScriptInterpreter, CLuaInterpreter, "lua");
 }

Implementations

lxlua.cpp

Add a string to a parent log, possibly returning a log entry for sub-entries

        static void 
 AddLogStringEntry (
        CLxLoc_LogEntry		&parent,
        const char		*s,
        LxResult		 code,
        int			 persist,
        CLxLoc_LogEntry		&entry)
 {
        ...
 }

Adding a log entry to a parent, for clients with no need for the entry.

 void
 AddLogString (
        CLxLoc_LogEntry		&parent,
        const char		*s,
        LxResult		 code,
        int			 persist)
 { 
        ...
 }

Logging for clients with no need for the entry.

        void
 LogString (
        const char		*s,
        LxResult		 code,
        int			 persist)
 {
        ...
 }

lxout: Output a string to the log

        int
 l_lxout(lua_State *L)
 {
        ...
 }

Internal function to perform queries and executions

 #define LXEVALMODEf_QUERY	1						// Query only; fail if no query is marked
 #define LXEVALMODEf_EXECUTE	2						// Execute only; fail if a query is marked
 #define LXEVALMODEf_EITHER	(LXEVALMODEf_QUERY | LXEVALMODEf_EXECUTE) 	// Query or execute, whatever works
 
        LxResult
 lxeval(lua_State *L, int mode)
 {
        ...
 }

At some point in the future, lxq() needs to use the raw string conversion feature to ensure that the values that are queried are the same kinds of values that get passed to commands.

        int
 l_lxq(lua_State *L)
 {
        ...
 }

lxqt(): Query a toggle command's toggle argument. Returns the query state on success (1 or 0), or 0 on failure.

        int
 l_lxqt(lua_State *L)
 {
        ...
 }

Return true if the last command executed/queried successfully.

        int
 l_lxok(lua_State *L)
 {
        ...
 }

Return the LxResult code of the last command executed or quieried.

        int
 l_lxres(lua_State *L)
 {
       ...
 }

When activated, lx() and lxq() data is output to the event log. If no args are passed in, this returns if tracing is active or not.

        int
 l_lxtrace(lua_State *L)
 { 
        ...
 }

Get the state of an option.

        int
 l_lxoption(lua_State *L)
 {
       ...
 }

Get the state of an option.

        int
 l_lxsetOption(lua_State *L)
 {
        ...
 }

Initialize the progress bar. Initialize the progress bar with n total steps. Returns 0 on error.

        int
 l_lxmonInit(lua_State *L)
 {
       ...
 }

Step the progress bar. Increment the progress bar by n steps, or 1 step if no steps are provided. This always returns true unless the user aborts, in which case it returns false.

        int
 l_lxmonStep(lua_State *L)
 {
       ...
 }

verifies that the file passed in is of the lua type.

        LxResult
 CLuaInterpreter::tsi_ValidateFileType (
        ILxUnknownID		  scriptObj,
        const char		 *firstLine)
 {
        ...
 }

intrlua.cpp

adds all of the functions just implemented in lxlua.cpp to CLuaInterpreter

        void
 CLuaInterpreter::Init()
 {
        ...
 }

performs most of the work, is the function that actually interprets the file.

        LxResult
 CLuaInterpreter::tsi_Run (
        ILxUnknownID		 scriptObj,
        int			 execFlags,
        const char		*args,
        ILxUnknownID		 messObj )
 {
        ...
 }

This function cleans up.

        void
 CLuaInterpreter::Kill()
 {
        ...
 
 }