2.9.16
Coherent GT
A modern user interface library for games
Sample - C++ and Lua binding

This sample demonstrates communication between an embedded in the game Lua virtual machine and the JavaScript engine in Coherent GT. The communication is in both directions and allows transparent converting between Lua and JavaScript data types.

Note
Supplied code is not representative for a well-designed, high-performance solution. It's written for simplicity and its purpose is to demonstrate features of Coherent GT.

Building the sample

The sample solution is based on a minimal game framework that provides basic functionality. The sample is already configured and you only have to compile and run it.

The output will be in the Coherent/bin directory.

Prerequisites

This sample builds upon the HelloGT sample and assumes that you understand it.

Key points

  1. Coherent::UIGT::ViewListener::OnReadyForBindings is overridden to allow registration of C++ handlers to be called from JavaScript.
  2. The debugger port is set inside the Coherent::UIGT::SystemSettings to allow easier development and troubleshooting.
  3. Converting from Coherent GT types to Lua types.
  4. Allowing Lua functions to change the UI.

Sample walk-through

The sample shows a command-prompt for entering a name of a function defined in the Lua universe as well as arguments (separated by space) for the function. When the Run button is pressed, the Lua function is executed and its result is displayed in the output window.

When the view is ready for the registration of C++ call and event handlers Coherent::UIGT::ViewListener::OnReadyForBindings is called

virtual void OnReadyForBindings() override
{
m_View->BindCall("lua", new LuaEventHandler(m_View, m_Lua));
m_View->BindCall("dispatch", Coherent::UIGT::MakeHandler(&LuaCallback));
}

This allows Lua functions to be called using ‘engine.call('lua’, ...)and for convenience the sample addsengine.lua` to be a shortcut for the above.

The HTML and JavaScript code for this sample is in Coherent/Samples/Sample_LuaBinding/uiresources/lua.html.

The Lua functions used by this example are in Coherent/Samples/Sample_LuaBinding/uiresources/game.lua

When the user clicks on the button 'Run' the input line is split by space, and `engine.lua(name, argument1, argument2, ...) is called.

This will call the LuaEventHandler::Invoke method with the specified arguments. The method does all the steps for executing the Lua function and returning the result:

  1. The name of the function is taken from the arguments and the Lua function is placed on top of the stack of the Lua virtual machine.
  2. The rest of the arguments are placed on the Lua stack using the ReadLuaValue function
  3. The Lua function is executed and the result (or error) is send back to the JavaScript.

ReadLuaValue peeks the type of the current argument and converts it to the corresponding Lua value, leaving the new value on top of the stack.

void ReadLuaValue(Coherent::UIGT::Binder* binder, lua_State* lua)
{
switch (binder->PeekValueType())
{
case Coherent::UIGT::VT_Null:
lua_pushnil(lua);
break;
case Coherent::UIGT::VT_Boolean:
{
bool jsValue = false;
binder->Read(jsValue);
lua_pushboolean(lua, jsValue ? 1 : 0);
}
break;
case Coherent::UIGT::VT_String:
{
const char* str = nullptr;
size_t length = 0;
binder->Read(str, length);
std::string nullterminated(str, length);
lua_pushstring(lua, nullterminated.c_str());
}
break;
...
}
}

To return the result back to JavaScript, the following snippet is used.

auto resultBinder = binder->ResultBegin();
ToJavaScript(m_Lua, lua_gettop(m_Lua), resultBinder);
binder->ResultEnd();

It will set the Lua value on top of the stack as the result of the call.

The ToJavaScript function is the reverse of ReadLuaValue - it takes the Lua value at specified index of the stack and exposes it to JavaScript using the Coherent::UIGT::Binder methods.

void ToJavaScript(lua_State* lua, int index, Coherent::UIGT::Binder* binder)
{
switch (lua_type(lua, index))
{
case LUA_TNIL:
binder->BindNull();
break;
case LUA_TBOOLEAN:
binder->Bind(lua_toboolean(lua, index) != 0);
break;
case LUA_TNUMBER:
binder->Bind(lua_tonumber(lua, index));
break;
case LUA_TSTRING:
{
binder->Bind(lua_tostring(lua, index));
}
break;
...
}
}

In the case of an error during the execution of the Lua function the JavaScript promise object is rejected using the Coherent::UIGT::View::SetScriptError method.

Higher Level APIs

The LuaEventHandler in the sample is directly using the Lua stack-based APIs. It is possible to use Coherent GT with higher level abstractions for Lua, such as luabind, LuaBridge or any in-house made APIs. The sample shows that with a very minimal abstraction for Lua values - LuaValue and the LuaCallback function which is exposed to Coherent GT.

In order for LuaValues to be sent to and received from JavaScript, Coherent GT has to know how to bind them. The following overloads allow Coherent GT to bind Lua values.

void CoherentReadInternal(Coherent::UIGT::Binder* binder, LuaValue& value)
{
value.State = g_LuaState;
ReadLuaValue(binder, value.State);
value.Reference = luaL_ref(value.State, LUA_REGISTRYINDEX);
}
void CoherentBindInternal(Coherent::UIGT::Binder* binder, const LuaValue& value)
{
lua_rawgeti(value.State, LUA_REGISTRYINDEX, value.Reference);
ToJavaScript(value.State, lua_gettop(value.State), binder);
lua_pop(value.State, 1);
}

This allows the LuaCallback function to be exposed to Coherent GT as any other C++ function.