Scripting API

From FreeSpace Wiki
Revision as of 07:36, 13 February 2006 by 72.194.192.77 (talk)
Jump to: navigation, search

The FS2_Open scripting API is intended to be as cross-platform and as language non-specific as possible. However, it is generally assumed that the most popular language will be Lua.

Part One: Basic API and Scripting Hooks

Things To Know

Script_system
This object abstracts a scripting session. It is generally used to allow for easy construction/destruction of whatever is necessary to initialize scripting system objects, and make sure that the right language session data is passed. Its constructor takes one argument, the name of the scripting session (for easy debugging)
Script_system.CreateLuaSession(libraries)
Initializes a Lua session. Until this is called, you will not be able to use Lua at all and fs2_open should ignore code chunks that use it.
Script_system.ParseChunk(debugname)
Parses a 'code chunk'. These are snippets of code encapsulated by symbols such as square brackets or parentheses. It stores a handle to the parsed script in memory (Probably bytecode).
script_system.RunBytecode(handle):Actually executes a code chunk that has been parsed with ParseChunk

Example

This snippet of code (using FS2_Open functions) will parse a file that has scripting after each "$Global:" variable identifier. After all the scripting has been parsed, it will evaluate every hook, once.

//Create script system object
script_system Script_system;

//Create array that holds a series of scripting hooks
std::vector<script_hook> Script_globalhooks;

//Initialize Lua
Script_system.CreateLuaState(Lua_libraries);

//Parse file
read_file_text("scripting.tbl");
reset_parse();
while(optional_string("$Global:"))
{
	Script_globalhooks.push_back(st->ParseChunk());
}

//Execute hooks
for(uint i = 0; i < Script_globalhooks.size(); i++)
{
	Script_system.RunBytecode(Script_globalhooks[i]);
}

Part Two: The Higher-Level Lua Library

The Lua library is probably what you'll want to be the most familiar with, as it's where you'll edit to add functions, classes, libraries, and variables.


Limitations

  • Lbraries can only contain functions, and not variables or arrays.
  • All "arrays" must be an object with an indexer (see LUA_INDEXER define).
    I was not able to get this working originally, but I've got another idea on how to try this so if you really feel an array class would be best in a given situation, tell me.


General conventions

For the internal Lua API:

  • All internal lua functions are prefixed with lua_
  • All Lua library and object variables are prefixed with l_, so "l_Graphics".
  • When failure occurs due to a bad type being passed, or a subsystem not being ready, LUA_RETURN_NIL is usually used.
  • When failure lies with the content of data, rather than the circumstances or type, LUA_RETURN_FALSE is generally used where appropriate.

For Scripting:

  • All variable names have the form of "EverySomethingIsCapitalized"
  • All function names have the form of "doSomethingFast()"
  • All object names have the form of "somethingneat"
  • All library names are one capitalized word. For more than one word, the same syntax as variables(?)
  • All library shortnames are two noncapitalized letters relating to the name. "Sound" -> "sd"
  • So far, I've used three-letter noncapitalized variables for globals. But I may decide to switch to the normal variable convention soon, as it's MUCH clearer about what the variable is.


Things To Know

lua_lib
Defines a Lua library.
lua_obj
Defines a Lua object type (userdata).
LUA_FUNC
Defines a Lua member function. Used with both lua_lib and lua_obj.
LUA_VAR
Defines a Lua member variable. Only used with lua_obj.
LUA_INDEXER
Defines Lua activity when brackets are used. Only used with lua_obj.

Once you invoke any of the above, it's not necessary to do anything else for it to be present in Lua.

lua_lib

lua_lib l_Base("Base", "ba", "Base Freespace 2 functions");

Defines a Lua library, which can contain member functions (But not any kind of variables).

Probably the simplest and most obvious to work with. The first field is the library name; the second is the shorthand version. The last is merely the description, which is only used for -ouput_scripting


lua_obj

lua_obj<object_h> l_Ship("ship", "Ship object", &l_Object);

Defines a lua object, via a C++ template. The <> defines what type of C data the object will hold. In general, it's better to pass a handle to Freespace data (such as the ship_info struct) than to try and copy the entire thing. For 'static' data - such as image handles or species indices - a simple <int> with the index of the item will suffice. For perishable data, you may want to store a unique identifier - say, an object's signature. In the above example, I use a helper struct that holds both a pointer to the object, and the object's signature. That way, all I need to do is check that the object at the pointer has the same signature to make sure that the handle is still valid.

The next two parameters are fairly obvious - the name of the object (becomes the metatable name), and the description in output_scripting.

The last parameter warrants some explanation - it is the parent of the object. Multiple levels of derived objects aren't supported, and a derived object must have the same type as its parent object, since all of the parent object's functions will generally only support the parent object's data type, and it's just needlessly complicating.


LUA_FUNC

LUA_FUNC(newVector, l_Math, "[x, y, z]", "Vector object", "Creates a vector object")

One of the two most used of the Big 5. The same tactics used for a LUA_FUNC are virtually the same for LUA_VAR and LUA_INDEXER.

There are two components to LUA_FUNC. The first is the arguments (above); the second is the actual body.

The arguments are, the function name (As it will appear in Lua). This is also used internally for the C compiler, so it is not actually a string. The second argument is the library or object that the function is a part of. So, the above example is the "newVector" function of the "Math" library, and would be accessed by calling "Math.newVector()"

The third, fourth, and fifth arguments are all purely descriptive, but you should always fill them in if applicable to ensure good documentation. The first is the arguments that the function will take; they may be in any form you please, but I have generally used the "required, required, [optional, optional]" form. As you can see, in the example all arguments are optional. The second argument is the return value. If the function returns different types, you should note it here. The last argument is the basic description. Because it can be pretty long, I've sometimes split it up across multiple lines.

Now, for the body of the define:

{
	vec3d v3 = vmd_zero_vector;
	lua_get_args(L, "|fff", &v3.xyz.x, &v3.xyz.y, &v3.xyz.z);
	return lua_set_args(L, "o", l_Vector.Set(v3));
}

Defining function variables

In the first line, we create the C vector object that we're going to return, and initialize it to 0 (since all arguments are optional).

Parsing arguments passed to your function in Lua

In the second line, lua_get_args is used to parse the arguments from Lua. This function is big and messy and takes care of all the warnings and errors that may be caused by invalid types and such. The first argument, L, is just the lua state data that must be passed along; ignore it but don't forget it. The second argument is a list of characters that specify the variable types. These characters represent the following C types:

  • b - Boolean
  • d - Double
  • f - Float
  • i - Int
  • s - String
  • x - Fix
  • o - lua_obj (See later)

Finally,

  • | - Denotes all variables after this as being optional.

All arguments after the formatting string should be pointers to the variables you want lua_get_args to set. Be careful; compilers will not catch if you use the a boolean where you have specified a double, for example. Optional variables will only be changed if a value is specified. Note: If a wrong type is passed to an optional variable of your function, that variable will be skipped, although variables after it may be changed. Note: You do pass pointers to (char *), so you end up passing a char **. Lua will take care of all memory concerns; don't try to deallocate the pointer yourself.

When using the 'o' parameter, you can get objects in one of two ways:

vec3d v3;
vec3d *v3p;
lua_get_args(L, "oo", l_Vector.GetPtr(&v3p), l_Vector.Get(&v3));

"v3p" would be a pointer to the first object's data, while "v3" would merely hold a copy of the data in the second object. Because it's not necessary to allocate or copy as much memory, getting pointers may be faster; but at the same time, you should be careful not to change the data in the pointer unless you really want to, as it will change the actual object's data. When you define a member function, the object will be passed as the first argument. As a result, this lua_get_args call would be suitable for, say, a function that copies the data passed in the argument to the calling object - "vectora:copyData(vectorb)".

lua_get_args will return the number of arguments parsed, or 0 if the required variables requirement was not met. So when working with required variables (or an object), you should generally use the form:

if(!lua_get_args(...))
	return LUA_RETURN_NIL;

The exception being when you want to actually know the number of arguments, in which case you should handle 0 appropriately. (More on LUA_RETURN_NIL later)

Return values

Return values are set via the lua_set_args function, which works pretty much exactly the same as lua_get_args, except in reverse. The differences:

  • Actual variables, rather than pointers, are passed.
  • Objects use their .Set() function (and there is no SetPtr() function).
  • No | character

Always use "return lua_set_args", or one of the following:

  • LUA_RETURN_NIL - Function returns nothing.
  • LUA_RETURN_FALSE - Function returns false.
  • LUA_RETURN_TRUE - Function returns true.

Yes, you can have multiple return values. The syntax in Lua is:

a, b = Library.theFunction() --In Lua

Operators

Definitely an advanced topic, which I will write when someone has need for it. These are slightly crazy as you can reverse objects and I don't fully understand what happens when you try to use an operator on two different objects that have different actions defined for that one operator.

Look at the vector class for what I've done with operators; if you got the LUA_FUNC section, you should be able to understand it. Note that the lua_isnumber() functions take the almighty L, and the argument index to check (Starting from 1).

Look up the lua_Operators[] array for the full list of operators, as well as a bit of descriptive text.


LUA_VAR

LUA_VAR(Name, l_Species, "String", "Species name")

Arguments are variable name, object that this variable will be a member of, the type that this variable is, and a brief description of what the variable is.

Variables defined by fs2_open are functions. This is generally the easiest and fastest way to go about doing things, since Lua is only a part of the much-larger fs2_open base, which cannot be going through the API to get at variables - not without causing massive code rewrites and slowdowns.

So, if you want an object to have actual data, you'll have to rely on the internal C dataset to do this. See the vector class for a good example.

Actually doing stuff with LUA_VAR

The two big differences with LUA_VAR are the LUA_SETTING_VAR macro, and how lua_get_args is used. Much like with functions, the first argument of lua_get_args will be the object that the variable is a member of. The second argument is optional, and is the value that the variable may be set to.

Here's the body for the above example, minus some safeguarding to make it easier to read:

	char *s = NULL;
	int idx;
	if(!lua_get_args(L, "o|s", l_Species.Get(&idx), &s))
		return LUA_RETURN_NIL;

	if(LUA_SETTING_VAR && s != NULL) {
		strncpy(Species_info[idx].species_name, s, sizeof(Species_info[idx].species_name)-1);
	}

	return lua_set_args(L, "s", Species_info[idx].species_name);

You should consider LUA_SETTING_VAR to be the safest way of determining whether or not a variable is actually being set, because it uses Lua's own internal guidelines to determine this.

It is good practice to return the value of the variable after changing it, so the scripter can check the value afterwards.


LUA_INDEXER

LUA_INDEXER(l_ShipTextures, "Texture name or index", "Texture", "Ship textures")

First argument is the object, second is what can be placed in the brackets ([]), third is the type returned, and fourth is a description of what the array is.

The first argument passed to the indexer will be the parent object; the second, the index (This can be whatever you like - a number, a string, even an image handle, just as long as lua_get_args can handle it). The third, the value that the scripter wants the current index to be set to.

In all other respects, it is exactly like LUA_VAR; use LUA_SETTING_VAR to determine whether you are in setting mode or not.