Skip to content
Denis Kuzmin [ github.com/3F ] edited this page Jan 30, 2019 · 5 revisions

Extending LuNari API in a 5 minutes

Please note: this page for everyone who wants to contribute to LuNari.

  • Not for end-users! Read this instead.

How does it work

Actual logic to Lua was based mainly on Direct binding. In turn, LuNari controls the all interaction with user-scope by API of upper-layer (ILua51, ILua52, ILua53, ...)

net.r_eg.LuNari.API

Key notes

"Wet" Interfaces

ILua_ interfaces are not inherited. It was in first alpha versions, however, modern LuNari follows a clear implementation within LSP principles.

Therefore, instead of compact "dry" interfaces (ILua53 -> ILua52 -> ILua51) we separated them into a self-sufficient unit. Now, this is fully compatible layer only for specific Lua version.

Lot of Lua functions will just repeat themselves from the previous interfaces. There is nothing easier than just using an inheritance for this case. But this is wrong way because of the following changes from different versions of Lua. For example: https://www.lua.org/manual/5.2/manual.html#8.3

  • lua_cpcall - removed in 5.2
  • lua_objlen - renamed, meet lua_rawlen in 5.2
  • Other changes like changed return-type for the same signatures, macros, etc.

As you can see, an inheritance will at least aggregate lot of obsolete functions in the higher child, like for ILua53, and other future versions. OR we must go against the LSP principle o_o' Are you ready?

Actually there are other options, like through decorators etc. But I really (n. the author of LuNari) don't want to complicate the logic for small things like this.

So, 'as is' the different interfaces for different libraries.

"Dry" Implementation

LuNari still uses the dry principle where it's possible.

Thus, implementation of interfaces (ILua51, ILua52, ILua53, ...) are dry as sand in the desert.

It provides Bridge<TAPI> for common LuaX layer for requests through Binder. Finally we have only "dry" code for each part of Lua version:

  • Func51;
  • Func52 (Only differences from Func51);
  • Func53 (Only differences from Func52);
  • ...

Binder covers the final layer to use generated logic for library via Core.IProvider (Conari engine)

LuNari Requirements for new Lua functions

  1. Please use only lambda-functions instead of DLR because of speed and moreover, this is more transparent for debug.
  2. LuNari provides LuNari.XML file for each public release. Thus, please comment all your covered Lua function with related information from Lua documentation. XML format, as follow:
/// <summary>
/// [-0, +1, -] int lua_rawgeti (lua_State *L, int index, lua_Integer n);
/// 
/// Pushes onto the stack the value t[n], where t is the table at the given index. 
/// The access is raw, that is, it does not invoke the __index metamethod.  
/// </summary>
/// <param name="L"></param>
/// <param name="index"></param>
/// <param name="n"></param>
/// <returns>the type of the pushed value.</returns>
public int rawgeti(LuaState L, int index, LuaInteger n)

Note: [-o, +p, x] [ ? ] Please add this + original signature of added lua function. Then basic description, and optionaly describe arguments/return value.

  • If it's a macro(macro definition, read below), please add related remark.

All this can be found in official documentation.

Here's API of Lua:

Map of Data types

net.r_eg.LuNari.Types;
net.r_eg.Conari.Types;
Lua arg in arg out / return
lua_State* LuaState LuaState
const char* string / CharPtr CharPtr
lua_Integer LuaInteger LuaInteger
lua_Number LuaNumber LuaNumber
lua_CFunction LuaCFunction LuaCFunction
int int int
size_t size_t size_t
... ... ...

Basic example

We'll add the lua_pushnumber.

As you can see Lua 5.1, 5.2, 5.3 contains same signature:

void lua_pushnumber (lua_State *L, lua_Number n);

So it's very simply to cover this:

  • Add the following equivalent into ILua51 interface (net.r_eg.LuNari.API.Lua51):
void pushnumber(LuaState L, LuaNumber n);
  • Then, just implement related binding for this in Func51, for example:
public void pushnumber(LuaState L, LuaNumber n)
{
    bind<Action<LuaState, LuaNumber>>("pushnumber")(L, n);
}

Note: Please avoid lua_ prefix from Lua function names because of used bind<>. For details, please read the documentation from Conari project.

After, we only need to expose similar signatures for the remaining versions in other available ILua interfaces (e.g. ILua52, ILua53, ...). Read about "Wet" Interfaces above.

[ Done ]

Example for different logic with identical signatures

For example, the lua_setglobal in Lua 5.1 defined as macro (yes, we need to add this because it's part of Lua API). However, starting with 5.2 it defined as normal C-exported-function with own logic.

That is, Lua 5.1, 5.2, 5.3:

void lua_setglobal (lua_State *L, const char *name);

For this or similar case we just need to implement it as virtual method and provide another logic in newer version.

[ Done ] But, please note the return-types restriction:

Different return-types

When we have the same signature with different return-types.

As you should know, we can't just override it, for example:

void lua_pushstring (lua_State *L, const char *s);
  • 5.2 & 5.3:
const char *lua_pushstring (lua_State *L, const char *s);

Solutions

The latest changes provides _.LuaState(LunaRoad: Rt_.) special type which is a simple wrapper to original LuaState.

As the most transparent way for end-users, now we can avoid this problems because LuaState is required arg for most cases.

However, you also should know about other possible ways:

  1. Replacing method only in child object: Be careful, it cannot be really overridden ('new' keyword). For our case it's also a correct option for API of upper layer. But anyway, it does not give guarantee the predictability or safety in future at all. Not to mention LSP... So, we will not.
  2. The most right way: Avoid classical horizontal inheritance by all abstract FuncX objects at all. For example, the Mixin pattern and Traits should help, or similar...
internal abstract class Func51: LuaX, ILua51

C macro definition

Some part of Lua API exists as macro definitions. But LuNari will provide support even for this. Therefore, we need to use the same internal logic instead of request to engine. For example:

void lua_setglobal (lua_State *L, const char *name);

Lua 5.1 declares lua_setglobal as macro:

#define lua_setglobal(L,s)   lua_setfield(L, LUA_GLOBALSINDEX, s)

But, starting from 5.2 it's c-exported function now.

Solution

Use the same declaration for all available ILua interfaces as:

void setglobal(LuaState L, string name);

Then, implement ILua51:

public virtual void setglobal(LuaState L, string name)
{
    setfield(L, LuaH.LUA_GLOBALSINDEX, name);
}

where setfield should point to already covered lua function:

public void setfield(LuaState L, int index, string k)
{
    bind<Action<LuaState, int, string>>("setfield")(L, index, k);
}

Now, implement ILua52 as normal request to Lua API:

public override void setglobal(LuaState L, string name)
{
    bind<Action<LuaState, string>>("setglobal")(L, name); // Only for Lua 5.2+
}

Finally ILua53+ will just use the latest ILua52 logic.

[ Done ]