Coder Social home page Coder Social logo

lua-bindings's Introduction

Lua-bindings

These are bindings intended to easily embed Lua into a C++11 project.

Everything is done by including a single header file, <lua-bindings/LuaState.hh>, then compiling against the static library created by the included SConstruct. The static library can also be built from the included SConscript, as part of a larger build.

Loading files

To load a file, use LuaState::LoadFile()

Lua::LuaState L;
L.LoadFile("some_file.lua");

Alternatively, use LuaState::LoadString() to directly load a string containing code.

Lua::LuaState L;
L.LoadString("print('hi')");

Loading Libraries

There are two functions available, LuaState::LoadLibs and LuaState::LoadSafeLibs. These each will load standard libraries available in Lua. However, LuaState::LoadSafeLibs will only load library functions that are safe for an untrusted user to have access to. The list of functions available in safe mode is as follows.

  • ipairs

  • pairs

  • print

  • next

  • pcall

  • tonumber

  • tostring

  • type

  • unpack

  • coroutine.create

  • coroutine.resume

  • coroutine.running

  • coroutine.status

  • coroutine.wrap

  • string.byte

  • string.char

  • string.find

  • string.format

  • string.gmatch

  • string.gsub

  • string.len

  • string.lower

  • string.match

  • string.rep

  • string.reverse

  • string.sub

  • string.upper

  • table.insert

  • table.maxn

  • table.remove

  • table.sort

  • math.abs

  • math.acos

  • math.asin

  • math.atan

  • math.atan2

  • math.ceil

  • math.cos

  • math.cosh

  • math.deg

  • math.exp

  • math.floor

  • math.fmod

  • math.frexp

  • math.huge

  • math.ldexp

  • math.log

  • math.log10

  • math.max

  • math.min

  • math.modf

  • math.pi

  • math.pow

  • math.rad

  • math.random

  • math.sin

  • math.sinh

  • math.sqrt

  • math.tan

  • math.tanh

  • os.clock

  • os.difftime

  • os.time

Getting/Setting Global Variables

The C++ code can read/write global variables to be available in Lua. When setting a variable, the type is automatically determined from the parameter.

Lua::LuaState L;
L.SetGlobal("x", 5);

When reading a variable, the type must be specified.

int x = L.CastGlobal<int>("x");

Functions

To call Lua functions from C++, use the LuaState::Call() method. Note the templated parameter which gives the return type of the function. If the Lua function returns a value that cannot be converted to the requested type, a LuaInvalidStackContents will be thrown.

Lua::LuaState L;
L.LoadString("function add_one(x) return x+1 end");
int output = L.Call<int>(42);

Lua functions can return multiple values. If the return value is requested as a std::tuple<>, then multiple values will be read.

Lua::LuaState L;
L.LoadString("function multiple_returns() return 5, 'hello' end");
std::tuple<int, std::string> output =
  L.Call<std::tuple<int, std::string> >("multiple_returns");

C++ functions can be registered, so that they can be called from within Lua.

int sum_numbers(int x, int y){
  return x+y;
}

Lua::LuaState L;
L.SetGlobal("sum_numbers", sum_numbers);

This works for any c-style function pointer or std::function.

Classes

To register a class, use LuaState::MakeClass(). Consider the following class.

class Example{
public:
  Example();
  Example(int x);

  int GetX();
  void SetX(int x);

private:
  int x;
};

To register it for use in Lua, one would use the following code.

Lua::LuaState L;
L.MakeClass<Example>("Example")
  .AddConstructor<>("make_Example")
  .AddConstructor<int>("make_Example_int")
  .AddMethod("GetX", &Example::GetX)
  .AddMethod("SetX", &Example::SetX)

Only the constructors and methods that are registered are available for use in Lua. This allows, for example, a class that can be passed in to Lua, but cannot be constructed from within Lua.

Lua Tables

Two types of tables are supported as parameters and return values, when calling functions across the C++/Lua boundary.

The first are tables with only numerical keys, and only a single value type. These can be converted to std::vector<T>.

Lua::LuaState L;
L.LoadString("function return_table() return {'a','b','c'} end");
std::vector<std::string> output =
   L.Call<std::vector<std::string> >("return_table");

The second are tables with only string keys, and only a single value type. These can be converted to std::map<std::string, T>.

Lua::LuaState L;
L.LoadString("function return_table() return {a=5, b=7, c=9} end");
std::map<std::string, int> output =
  L.Call<std::map<std::string, int> >("return_table");

Lua Coroutines

A function can be started as a Lua coroutine, rather than running directly. The main advantage is that coroutines can be yielded, while a function call cannot.

Lua::LuaState L;
L.LoadSafeLibs();
L.LoadString("function yields() print('First') coroutine.yield() print('Second') end");

auto coroutine = L.NewCoroutine();
coroutine.LoadFunc("yields");
coroutine.Resume(); // Prints 'First'
coroutine.Resume(); // Prints 'Second'

In addition, coroutines can have limits placed on the number of Lua instructions that can be run. This prevents user scripts from suspending the program if they infinite loop.

Lua::LuaState L;
L.LoadString("function infinite_loop() while true do end");

auto coroutine = L.NewCoroutine();
coroutine.LoadFunc("infinite_loop");
coroutine.SetMaxInstruction(10000);
coroutine.Resume(); // throws a LuaRuntimeTooLong.

Any arguments passed to LuaCoroutine::Resume() are given as the return values of coroutine.yield(). Similarly, any returns requested from LuaCoroutine::Resume() will give the value passed into coroutine.yield(), or the value of the return statement if the function finishes. The IsFinished() method can be used to determine whether a coroutine is completed, or has just yielded.

Lua::LuaState L;
L.LoadSafeLibs();
L.LoadString("functions yields_return(x) y = coroutine.yield(x) return y end");

auto coroutine = L.NewCoroutine();
coroutine.LoadFunc("yields_return");
auto return1 = coroutine.Resume<int>(5);
assert(!coroutine.IsFinished());
auto return2 = coroutine.Resume<std::string>("hello");
assert(coroutine.IsFinished());

lua-bindings's People

Contributors

lunderberg avatar

Stargazers

ckytam avatar

Watchers

James Cloos avatar  avatar

lua-bindings's Issues

Look into LuaJIT

From what I can tell, LuaJIT is awesome, and should be available as an option. Right now, I am targeting lua 5.3.0. LuaJIT is only compatible up to lua 5.1. How much of the bindings rely on things beyond that point?

Better stack management with LuaObject

Right now, objects stay on the stack forever, unless manually used or popped. There should be some sort of automatic object management. The problem comes that any deletion may invalidate the stack_index of other LuaObjects, since removal from the middle of the stack causes everything above it to shift down.

Options:

  • Keep objects entirely in C++ memory, push onto the lua stack as needed.
  • Watch each function that could modify the stack, update the stack indices as they happen.

Put everything inside "Lua" namespace

There are a few things that still aren't inside the Lua namespace. The LuaRegistryNames and LuaExceptions come to mind. Make sure that I don't put anything in the global namespace.

Garbage collection of functions

Right now, even if a temporary function is passed as a parameter to a lua function, after which it is no longer accessible from lua, it still stays in the LuaState forever.

Look into how lua handles garbage collection, and whether the function can be given entirely to lua to manage.

Throw the correct expections from inside lua_pcall

Right now, lua_pcall catches exceptions thrown from my C++ functions, then sends those along as a lua object. From there, the exception is attempted to be read as a string, which fails. Make the exception thrown from a LuaState::Call be the same exception thrown from the inner function call.

Add some unit tests already

The code is getting altogether too complicated. I am looking at the results of some manual tests after each change, but what a pain it is.

Safe Reference Passing

Right now, references can be passed to Lua function calls. However, it is not safe to do so for an untrusted script, because the script may save a copy of the reference to be used in a later function call when the pointer is no longer valid. Therefore, there must be some way to invalidate a C-style pointer at the end of the function call.

Possible implementation:

  1. Add an int member to VariablePointer.
  2. When passing by reference, give this int member a unique value.
  3. Store a (int -> bool) table in the Lua registry. If a key is present in the table, then the reference is still valid.
  4. When calling a method on the object, check to see if the key is present.
  5. When the function call completes, remove the key from the table. This will be the tricky part, because Lua::Push gives no indication of what it pushed. Perhaps saving the current state of the (int -> bool) table, then restoring it afterward.

Proper alignment of C++ types

RIght now, C++ objects are stored in whatever memory is returned by lua. Instead, the C++ objects should be properly aligned, using alignas and alignof.

Passing C++ objects into Lua functions

At the moment, C++ objects can be created by Lua, and can have methods called from within Lua. However, functions cannot be called with C++ objects as arguments. Brainstorming a few ideas to figure out how.

  1. On passing a C++ object, copy into Lua memory. Lua then controls access to the object. On return of a C++ object, constructs a copy.
    1. Avoids memory management issues.
    2. Could result in expensive copying.
  2. Increase the userdata size from sizeof(pointer) to sizeof(pointer) + sizeof(bool). The extra boolean will be used to indicate whether the object is owned by C++ or by Lua.
    1. Allowing access in Lua to anything that is memory controlled in C++ seems risky, without any solution. If the C++ side decides that the object is to be destructed, the Lua user code may have already stored a reference somewhere.
  3. Only ever store a std::shared_ptr as the userdata, and only allow passing of objects into Lua as a shared_ptr. This allows for an object to be freely passed between the C++ and Lua boundary, only being deleted when neither of them uses it any more.
    1. Would require for all objects being passed in to be declared as shared_ptr.
  4. Mix of 1 and 3. Only store things by shared_ptr. If an object is passed as a shared_ptr, then share it between the C++ and Lua side. If an object is passed/returned by value, then construct it on the other side.
    1. Seems the safest route, as it would prevent
    2. May cause extra memory usage, as shared_ptr has some overhead.

Grab variable as std::function

I want to be able to output any function as a std::function, to be called from C++ without needing to directly interface with the Lua library.

  1. Class should hold a std::shared_ptr to something to ensure that the lua_State does not expire while the function exists. Now that I think of it, LuaThread should do the same.
  2. The lua object should be stored in the registry so that it won't be garbage collected early.
  3. The object held by the std::function should have a destructor that removes the reference from the registry.

Lua, hold objects by weak_ptr, c style pointer

In some cases, for memory management reasons, I may want an object passed in to be held by weak_ptr, or even by c style pointer. Each section of user data should have two fields, first indicating the method in which it is stored, then storing it.

Returning table from C++ function

If a C++ function returns either a std::vector or a std::map<std::string, T>, it should be pushed as a table. If I feel like handling the mixed case of a lua table with both an array part and a hash part, I may make a special type for it.

LoadFile/script in coroutine

Since the loading of a file could have an infinite loop, this should be done in a non-main thread. It might be easiest to make LuaCoroutine be a protected subclass of LuaState, then expose some of the methods that LuaState provides.

Compatibility with clang

When first writing this package, I only tested it with gcc. It fails to compile under clang, and should be modified to be compatible with both.

Compile lua with C++

Right now, the lua interpreter uses setjmp/longjmp for error handling, because it was compiled as a C library. Switch to compiling it with C++, because exceptions are much easier to handle.

Lua::Cast<int> throws when it shouldn't

When returning multiple integer values from a lua function and converting to a std::tuple, lua_isnumber sometimes returns false when it should return true. Investigate this.

Use the luaL_check* functions

For example, rather than having as isnumber/getnumber pair, I should use the checknumber function, which does both.

Correctly destroy LuaCoroutines

Initially, I thought that the coroutines made by lua_newcoroutine were only C constructs. Reading more, these are placed on the stack. In my current implementation, these are just left there.

I should save the coroutines in a table in the registry, then remove them when destructed. Use luaL_ref in constructor and luaL_unref in destructor.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.