Difference between revisions of "Making a Plugin"

From GiderosMobile
Line 139: Line 139:
 
//This is a list of functions that can be called from Lua
 
//This is a list of functions that can be called from Lua
 
const luaL_Reg functionlist[] = {
 
const luaL_Reg functionlist[] = {
{"addTwoIntegers", addTwoIntegers}, {NULL, NULL}, };
+
{"addTwoIntegers", addTwoIntegers}, {NULL, NULL},
 +
};
 
luaL_register(L, "myFirstPlugin", functionlist);
 
luaL_register(L, "myFirstPlugin", functionlist);
 
//return the pointer to the plugin
 
//return the pointer to the plugin

Revision as of 18:56, 21 April 2023

The Ultimate Guide to Gideros Studio

Lua Based Plugins

If your plugin is 100% Lua code then you can easily make it into a plugin.

Just create a folder based on the name of the plugin and add a file (also based on the plugin name) with the extension .gplugin .

The contents of the file should look like this:

<plugin name="My plugin name" description="What my plugin does!">
<target name="APK,iOS,Html5,WinRT,Windows,MacOSX,Win32"></target>
</plugin>

Also add a sub-folder called 'luaplugin' to your folder, put your Lua files in there. These will be automatically added to a project that adds your plugin. You can run them by using the Lua 'require' command.

You can test them as userplugins (click add plugin, then click user plugins to find where to add them) then possibly submit them using github to include in the distribution. If you do submit them for inclusion then please try make the name of the plugin unique to avoid confusion with other similar plugins - eg don't call it UI, but rather give it a name like razorUI ...

Native 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.

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

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
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.

#include "gideros.h"
#include "lua.h"
#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:

Plugins flowchart.png


PREV.: Exporting to a Player
END