Extend your application with plugins

From GiderosMobile
Revision as of 03:20, 8 July 2019 by MoKaLux (talk | contribs) (added content for Extend your application with plugins)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

The Ultimate Guide to Gideros Studio

Extend your application with plugins

Introduction to Plugins

Using plugins written in the native language of the iOS or Android device, you can extend Gideros to use all the native features of the device. There is an example iOS plugin included with Gideros Studio for Game Kit. This means that your Gideros application can fully use Apple’s GameCenter.

Top three layers.png

Referring back to the architecture described in chapter 1.5, this chapter will deal with the top three layers.
Gideros Studio <--------> Lua - C++ bridge <--------> 3rd party plugins

The Lua - C++ bridge API

Lua provides a full C API interface, which allows communication between Gideros Studio and your plugin. Gideros Studio and your plugin exchange data through a stack.
Data is pushed onto the stack, and popped off the stack. In this example, an integer has just been pushed onto the stack, and is at the top of the stack.

4 integer -1
3 table -2
2 function -3
1 “string” -4
  The Lua Stack

<\br>

Notice the numbering - on the left is the position in the stack, 1 being the deepest. On the right is the relative numbering from the top, -1 being the top, and -4 here being the deepest.

A pseudo-code example

This all takes place in the C API plugin code.

Push “fred” onto an empty stack 1 “fred” -1
Push a function onto the stack 2 function -1
1 “fred” -2

XXX

Push a new table onto the stack 3 table (empty) -1

2 function -2

1 “fred” -3

Push an integer onto the stack 4 integer -1

3 table (empty) -2

2 function -3

1 “fred” -4

Move the integer into the table 3 table (contains integer) -1

2 function -2

1 “fred” -3

Pop 1 off the stack 2 function -1

1 “fred” -2

Pop 2 off the stack

<stack empty>


A first plugin for the iPhone

Gideros Studio will send the plugin two integers, which a C function will add together and return the result to Gideros Studio.

   1. In Xcode, open the GiderosiPhonePlayer project and create an empty file called myFirstPlugin.mm.
   2. Copy in the plugin code below.
   3. Connect the iPhone, and build and run the GiderosiPhonePlayer app on the phone. The FPS is printed on the debug console every second.
   4. Create a new project in Gideros Studio
   5. In Gideros Studio, under Player Menu > Player Settings, untick Localhost, and enter the IP address on your iPhone (wireless connection is needed here for Gideros Studio). The IP address should look something like 192.168.x.xxx.
   6. Copy in the Gideros Studio code below.
   7. Run the program in Gideros Studio.

You should have both the debug console open in Xcode, and the Output console in Gideros Studio. Messages from the plugin will be printed in the Xcode console, and messages from Lua will be printed in the Gideros Studio console.

Plugin code

Create an empty file called myFirstPlugin.mm in the GiderosiPhonePlayer Xcode project, and copy the following code into it. This is the minimum code that is necessary for a C plugin.

  1. include "gideros.h"
  2. include "lua.h"
  3. include "lauxlib.h"

static int addTwoIntegers(lua_State *L) { //retrieve the two integers from the stack int firstInteger = lua_tointeger(L, -1); int secondInteger = lua_tointeger(L, -2); int result = firstInteger + secondInteger; //place the result on the stack lua_pushinteger(L, result); //one item off the top of the stack, the result, is returned return 1; } static int loader(lua_State *L) { //This is a list of functions that can be called from Lua const luaL_Reg functionlist[] = { {"addTwoIntegers", addTwoIntegers}, {NULL, NULL}, }; luaL_register(L, "myFirstPlugin", functionlist); //return the pointer to the plugin return 1; } static void g_initializePlugin(lua_State* L) { lua_getglobal(L, "package"); lua_getfield(L, -1, "preload"); lua_pushcfunction(L, loader); lua_setfield(L, -2, "myFirst"); lua_pop(L, 2); } static void g_deinitializePlugin(lua_State *L) { } REGISTER_PLUGIN("myFirstPlugin", "1.0")

Gideros Studio code

Create a new Gideros Studio project, and then a main.lua file containing the following code. This is the minimum code that will interface with the C plugin.

require "myFirst" --Send two integers to be added together and return an integer result local result = myFirstPlugin.addTwoIntegers(14, 48) print("\n\nSample 1 - Result of addTwoIntegers:", result, "\n\n")

Explanation of the code

require "myFirst" This Lua code calls the g_initializePlugin() function, which tells the Gideros API to initialize and call the loader() function. The loader() function tells the Gideros API which plugin functions are available to be called from Lua. When you create a new function to be called from Lua, add that function to the functionList[] in loader(). If you get the message "attempt to call method xxx (a nil value)", then you have probably forgotten to add the function to the list.

local result = myFirstPlugin.addTwoIntegers(14, 48)

This Lua code will call the plugin function addTwoIntegers(). Plugin functions are always declared:

static int function_name(lua_State *L) { return noOfArguments;}

noOfArguments is the number of arguments held on the stack to return to Gideros Studio. Declare the plugin function:

static int addTwoIntegers(lua_State *L) { lua_State *L holds all the information of the Lua interpreter, and must be passed as the first argument. int firstInteger = lua_tointeger(L, -1); int secondInteger = lua_tointeger(L, -2); int result = firstInteger + secondInteger; The two integers sent from Gideros Studio - 14 and 48 - are on the top of the stack. 48 is the top position (-1) and 14 is the second position (-2). lua_tointeger(lua_State *L, int index) is one of the Lua C API functions that retrieves data from the Lua stack. //place the result on the stack lua_pushinteger(L, result); lua_pushinteger(lua_State *L, int i) places the result back onto the stack in the top position. //one item off the top of the stack, the result, is returned return 1;

The number of arguments to be retrieved off the top of the stack are returned to Gideros Studio. As the result is the topmost position on the stack, this is the one that Gideros Studio will retrieve.

Exchanging Data

The most common Lua C API functions will be described below. For a complete listing of all the functions in the Lua C API, visit the Lua manual at: http://www.lua.org/manual/5.1/manual.html#3.7

Send data to the plugin

In the addTwoIntegers example above, two integers are sent to the plugin, and the plugin extracts them off the stack using: lua_tointeger(L, -1); Other types of data can be extracted using the other lua_totypes: lua_tonumber(L, -1); lua_toboolean(L, -1); lua_tostring(L, -1);

Return data to Gideros Studio

When the plugin function is complete, the function will return to Gideros Studio with a number of arguments:

return 2;

This will return two values off the top of the stack. To get data onto the stack, use one of the lua_pushtype functions:

lua_pushstring(L, “this is a string”); lua_pushinteger(L, result); lua_pushboolean(L, isCorrect); lua_pushnumber(L, afloat);

Tables

To be used in the C plugin, Lua tables have to be converted to a C type, such as an array or an NSArray or an NSDictionary. To traverse a table on the top of the stack:

//push nil onto the stack so that the while loop will work lua_pushnil(L); //==2 nil -1== //==1 tableWithKeys -2== while (lua_next(L, 1) != 0) { //lua_next pops the key off the stack //then pushes the next key and value onto the stack //==3 value - table value -1== //==2 key - key -2== //==1 tableWithKeys -3== //use a function like lua_tonumber to retrieve the value //off the top of the stack // remove value - key stays for lua_next lua_pop(L, 1); //==2 key - key -1== //==1 tableWithKeys -2== }

It is a good idea to keep track of the stack in comments, as in the example above, so that you can easily see what is happening in your code.

Example - Sort a Gideros Studio table array

This example sends an array of strings from Gideros Studio to the plugin. The plugin reads the table, and places the array into an Objective C NSMutableArray. It is then sorted and returned to Gideros Studio. Plugin Code Remember to add the name of the function to the function list in loader() as described above. static int sortTable(lua_State *L) {

       //==1 tableToBeSorted -1==
       //push nil onto the stack so that the while loop will work
        lua_pushnil(L);
   
       //==2 nil -1==
       //==1 tableToBeSorted -2==
   
       NSMutableArray *array = [NSMutableArray array];
       //move the table into an NSMutableArray
       while (lua_next(L, 1) != 0) {
           //==3 value - sort this string -1==
           //==2 key - in an array, this is the index number -2==
           //==1 tableToBeSorted -3==
           NSString *value = [NSString stringWithUTF8String:luaL_checkstring(L, -1)];
           [array addObject:value];
                   // remove value - key stays for lua_next
           lua_pop(L, 1);
       }
       //pop the table off the stack, as we don't need it any more
       lua_pop(L, 1);
       //sort the array
       [array sortUsingSelector:@selector(compare:)];
       //Create a new table
       lua_newtable(L);
       //==1 newtable -1==
       //Lua tables start from 1, whereas C arrays start from 0
       int index = 1;
       for (NSString *string in array) {
           //push the table index
           lua_pushnumber(L, index++);
           //push the string on the stack, converting it to a const char*
           lua_pushstring(L, [string UTF8String]);
           //==3 "animal string" -1==
           //==2 index number            -2==
           //==1 newtable             -3==
           //store index and string value into the table
           lua_rawset(L, -3);
           //==1 newtable            -1==
       }
       //return the sorted table on the stack to Gideros Studio
       return 1;

}

Gideros Studio code require “pluginName” local sortedResult = pluginName.sortTable({"giraffe", "antelope", "aardvark", "zebra", "badger"}) for i = 1, #sortedResult do

   print("\t\t", sortedResult[i])

end

Communication from the plugin to Gideros Studio

Communication from the plugin to Gideros Studio can be achieved by direct calling of a Gideros Studio function in the plugin, and also by dispatching events, which a Gideros Studio listener will pick up.

Call a Gideros Studio function from the plugin

This example will define two functions in Gideros Studio - printName() and eventHappened(). The C plugin will call these two functions. Gideros Studio Code: local function printName(name)

   print("\n\t\tFunction called from plugin")
   print("\t\t", "Name:", name)

end local function eventHappened(event, x, y)

   print("\n\t\tFunction called from plugin")
   print("\t\t", "Event:", event, "X:", x, "Y:", y)

end print("\nSample 7 - Call functions from plugin:") pluginName.doCallback(printName, "Caroline", 1) pluginName.doCallback(eventHappened, "Touched", 52, 243, 3)

Plugin code: The function doCallback() accepts the function name to be called, then the arguments for that function, and then the number of arguments sent. static int doCallback(lua_State *L) {

       //==4 no of arguments -1==
       //==3 argument -2==
       //==2 argument -3==
       //==1 function -4==
       int noOfArguments = lua_tointeger(L, -1);
       //remove no of arguments off the stack
       lua_pop(L, 1);
       //call the function.
       //noOfArguments tells the Lua C API how many arguments are 
       //on the stack.
       //the function is on the stack below all the arguments
       lua_call(L, noOfArguments, 0);
       return 0;

}

Retain data in the Lua Registry

To be able to retain information, the Lua C API provides a registry. This behaves like the stack, but has a special index called LUA_REGISTRYINDEX. You can create tables and push them into the registry by

   lua_rawset(L, LUA_REGISTRYINDEX)

Because the registry is common over all available plugins, the usual way of creating a unique key for a registry table is to use the address of a variable. This example saves data into a KEY_OBJECTS table that has been previously created in the registry. static const char KEY_OBJECTS = ' '; lua_pushlightuserdata(L, (void *)&KEY_OBJECTS); //== 1 address of KEY_OBJECTS -1== lua_rawget(L, LUA_REGISTRYINDEX); //== 1 table of KEY_OBJECTS -1== lua_pushstring(L, “value”); //== 2 “value” -1== //== 1 table of KEY_OBJECTS -2== //”value” goes into KEY_OBJECTS at index 1 lua_rawseti(L, -2, 1); //== 1 table of KEY_OBJECTS -1== Retrieving this data - "value" will be placed at the top of the stack for further use: lua_pushlightuserdata(L, (void *)&KEY_OBJECTS); //== 1 address of KEY_OBJECTS -1== lua_rawget(L, LUA_REGISTRYINDEX); //== 1 table of KEY_OBJECTS -1== //retrieve index 1 in the KEY_OBJECTS table lua_rawgeti(L, -2, 1); //== 2 “value” -1== //== 1 table of KEY_OBJECTS -2==

Dispatch events to Gideros Studio

Events and listeners are one of the core features of Gideros Studio. To set up the C API to dispatch events, you need to create a sub-class of GEventDispatcherProxy. This class will be retained in the Lua registry as described above. The following example will build a ProgressBar class, which will accept an integer from Gideros Studio, add it to a total held in the ProgressBar class, and when the total reaches 100, will dispatch an event to Gideros Studio. This loader function will: 1. Create an Event Dispatcher class 2. Define an event and save it in the Lua global Event table 3. Create a table in the Lua registry to retain variables 4. Instantiate the Event Dispatcher class 5. Retain the Event Dispatcher class instance in the registry table

static int loader(lua_State *L) { //list the functions available to Gideros Studio const luaL_Reg functionlist[] = { {"addToProgress", addToProgress}, {NULL, NULL}, }; // 1. Create an Event Dispatcher class, defining optional //create and destruct methods g_createClass(L, "ProgressBar", "EventDispatcher", NULL, destruct, functionlist); // 2. Define an event and save it in the Lua global Event table lua_getglobal(L, "Event"); lua_pushstring(L, "progressComplete"); lua_setfield(L, -2, "progressComplete"); lua_pop(L, 1); // 3. create a weak table in LUA_REGISTRYINDEX that can be accessed with the address of KEY_OBJECTS lua_pushlightuserdata(L, (void *)&KEY_OBJECTS); lua_newtable(L); // create a table lua_pushliteral(L, "v"); lua_setfield(L, -2, "__mode"); // set as weak-value table lua_pushvalue(L, -1); // duplicate table lua_setmetatable(L, -2); // set itself as metatable lua_rawset(L, LUA_REGISTRYINDEX); // 4. Instantiate the Event Dispatcher class ProgressBar *bar = new ProgressBar(L); g_pushInstance(L, "ProgressBar", bar->object()); // 5. Retain the Event Dispatcher class instance in the registry table lua_pushlightuserdata(L, (void *)&KEY_OBJECTS); lua_rawget(L, LUA_REGISTRYINDEX); lua_pushvalue(L, -2); lua_rawseti(L, -2, 1); lua_pop(L, 1); lua_pushvalue(L, -1); lua_setglobal(L, "progress"); //return the pointer to the plugin return 1; }

The addToProgress function, listed in the plugin’s function list, simply receives an integer from Gideros Studio and adds it to the ProgressBar’s totalProgress variable:

static int addToProgress(lua_State *L) {

   //how to access an internal instance - see below
       GReferenced* object = static_cast<GReferenced*>(g_getInstance(L, "ProgressBar", 1));
   ProgressBar* bar = static_cast<ProgressBar*>(object->proxy());
       int progress = lua_tointeger(L, -1);
       bar->totalProgress += progress;
   //remove the received integer off the stack
       lua_pop(L, 1);
   //check to see if totalProgress is greater than 100
   //if it is, dispatch an event to Gideros Studio.
       bar->checkProgress(L);
       return 0;

}

The loader function creates a ProgressBar class, which inherits from the Gideros class GEventDispatcherProxy. Proxies are the abstract classes for implementing new types of Gideros objects. Each proxy class owns an instance of an internal class. For example, when you create a GEventDispatcherProxy instance: GEventDispatcherProxy* eventDispatcherProxy = new GEventDispatcherProxy(); you can access the internal instance by using the object() function: GReferenced* eventDispatcher = eventDispatcherProxy->object(); Or, if you have a internal object pointer, you can access the proxy object like: GReferenced proxy = eventDispatcher->proxy(); Functions like g_pushInstance(), g_getInstance() or destruct() work with internal object pointers. To place an object on the stack: g_pushInstance(L, "GameKit", gamekit->object()); Or to retrieve: GReferenced* object = static_cast(g_getInstance(L, "GameKit", index)); GameKit* gamekit = static_cast(object->proxy()); Similarly, in the destruct function, you first get the internal object pointer and then get the proxy pointer from it: void* ptr = *(void**)lua_touserdata(L, 1); GReferenced* object = static_cast(ptr); GameKit* gamekit = static_cast(object->proxy()); The ProgressBar class holds a totalProgress variable, and has two methods checkProgress, to see if totalProgress is greater than 100 dispatchEvent, which dispatches the event to Gideros Studio: class ProgressBar : public GEventDispatcherProxy { public: int totalProgress; ProgressBar(lua_State* L) : L(L)

          {    
                   totalProgress = 0;

}

           ProgressBar()
           {    }
           void dispatchEvent(const char* type,
                                          NSError* error,
                                          NSString *returnMessage)
           {
               //== Stack contents
                  //==1 plugin details -1==
         lua_pushlightuserdata(L, (void *)&KEY_OBJECTS);
                  lua_rawget(L, LUA_REGISTRYINDEX);
                   lua_rawgeti(L, -1, 1);
                   //==3 Registry table -1==
                   //==2 Key Objects table -2==
                   //==1 plugin details -3==
                   if (!lua_isnil(L, -1))
                   {
                       //get the function name from the Registry
                       //for dispatchEvent
                           lua_getfield(L, -1, "dispatchEvent");
                       //==4 function for dispatchEvent -1==
                       //==3 Registry table -2==
                       //==2 Key Objects table -3==
                       //==1 plugin details -4==
                       //copy the Registry table to the top of the stack
                           lua_pushvalue(L, -2);
                       //==5 Registry table -1==
                       //==4 function for dispatchEvent -2==
                       //==etc
                       //get the event strings for "Event"
                       //these were set up in loader()
                       //"Event" currently only contains “progressComplete”
                           lua_getglobal(L, "Event");
                       //==6 Event table -1==
                       //==5 Registry table -2==
                       //==4 function for dispatchEvent -3==
                       //==etc
                       //get the Event function associated with the string "new"
                           lua_getfield(L, -1, "new");
                       //==7 function -1==
                       //etc
                       //remove the Event table
                       lua_remove(L, -2);
                       //==6 function -1==
                       //==5 Registry table -2==
                       //==4 function for dispatchEvent -3==
                       //==3 etc
                       //push the event type ("progressComplete" in this example)
                           lua_pushstring(L, type);
                       //==7 "progressComplete" -1==
                       //==6 function -2==
                       //==5 Registry table -3==
                       //==4 etc
                       //call the "new" event function
                       //with "progressComplete" as the argument
                       //One return value is placed on the stack
                           lua_call(L, 1, 1);
                       //==6 table returned from "new" -1==
                       //==5 Registry table -2==
                       //==4 function for dispatchEvent -3==
                       //==etc
                          if (error)
                           {
                                  lua_pushinteger(L, error.code);
                                   lua_setfield(L, -2, "errorCode");
                                lua_pushstring(L, [error.localizedDescription UTF8String]);
                                   lua_setfield(L, -2, "errorDescription");                                    }
                           if (returnMessage)
                           {
                           //any arguments are pushed into the table
                           //returned from "new"
                           //this table will be sent to the Lua Event function
                           lua_pushstring(L, [returnMessage UTF8String]);
                           lua_setfield(L, -2, "stringReturned");
                           }
                       //two tables are sent to the
                       //dispatch event function
                           if (lua_pcall(L, 2, 0, 0) != 0)
                           {
                                   g_error(L, lua_tostring(L, -1));
                                   return;
                           }
                   }
                   lua_pop(L, 2);
           }
   
       void checkProgress(lua_State *L) {
                   if (totalProgress >= 100) {
                       dispatchEvent("progressComplete", NULL, @"Progress Complete");
                   }

} private:

   lua_State* L;

};

When the ProgressBar was first declared to Gideros: g_createClass(L, "ProgressBar", "EventDispatcher", NULL, destruct, functionlist); destruct() was the function listed to be called to clean up the memory. (NULL in that example could have been a function to be called when creating the class): static int destruct(lua_State* L) {

   //get the internal object pointer
   void *ptr = *(void**)lua_touserdata(L, 1);
   //get the proxy pointer
   GReferenced *object = static_cast<GReferenced*>(ptr);
   ProgressBar *bar = static_cast<ProgressBar*>(object->proxy());
   bar->unref();
   return 0;

}

The Gideros Studio code to use this plugin is simple: require "progress" --this function will be called when the --"progressComplete" event is dispatched function onProgressComplete(event)

   print("Progress Complete")

end progress:addEventListener("progressComplete", onProgressComplete) --totalProgress will be 10 progress:addToProgress(10) --totalProgress will be 30 --this is an equivalent way of calling the plugin function ProgressBar.addToProgress(progress, 20) --totalProgress will be greater than 100, and an event will be --dispatched, calling onProgressComplete progress:addToProgress(94)

This flowchart summarizes the flow of control between Gideros Studio, the C plugin and the ProgressBar C++ class for data exchange and event dispatching: