Created
January 20, 2019 06:25
-
-
Save LunaTheFoxgirl/03c8b7465808174ec64bf9cb5e71cb11 to your computer and use it in GitHub Desktop.
Entire lua wrapper as it is now.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module wereshift.engine.scripting.lua; | |
import lua; | |
import std.string; | |
import std.traits; | |
import std.conv; | |
import polyplex.utils.strutils; | |
import std.variant; | |
private __gshared bool isInitialized; | |
alias LuaStateFunction = lua_CFunction; | |
alias LuaStatePtr = lua_State*; | |
__gshared string LUA_PATH = "content/scripts/?.lua"; | |
/// Expose this to Lua via LuaUserData | |
struct Expose; | |
class LuaException : Exception { | |
public: | |
this(string message, string origin) { | |
super("<{0}> from {1}".Format(message, origin)); | |
} | |
} | |
/// Gets a new Lua state. | |
LuaState newState() { | |
import std.process; | |
// Make sure that lua is only loaded once in to memory. | |
if (!isInitialized) { | |
bool loaded = loadLua(); | |
} | |
lua_State* sptr = luaL_newstate(); | |
environment["LUA_PATH"] = LUA_PATH; | |
// Bind lua libs | |
// Sandbox away potentially dangerous stuff for now. | |
/*luaopen_base(sptr); | |
luaopen_string(sptr); | |
luaopen_math(sptr); | |
luaopen_table(sptr); | |
// This only works via lua calls ¯\_(ツ)_/¯ | |
// Tbh this might be a little dangerous, but its what i can do rn. | |
// Also pushcfunction doesn't take extern(C) elements so we do some dumb casting. | |
lua_pushcfunction(sptr, cast(LuaStateFunction) luaopen_package); | |
lua_pushliteral(sptr, LUA_LOADLIBNAME); | |
// This is named wrong but w/e | |
luaL_call(sptr, 1, 0);*/ | |
luaL_openlibs(sptr); | |
return new LuaState(sptr); | |
} | |
/// Disposes lua from memory, run on exit. | |
/// or not, whatever floats your goat. | |
void disposeLua() { | |
unloadLua(); | |
} | |
enum IsInteger(T) = (is(typeof(T) == byte) || is(typeof(T) == short) | |
|| is(typeof(T) == int) || is(typeof(T) == long) || is(typeof(T) == lua_Integer)); | |
enum IsNumber(T) = (is(typeof(T) == float) || is(typeof(T) == double) || is(typeof(T) == lua_Number)); | |
enum IsBoolean(T) = (is(typeof(T) == bool)); | |
enum IsUnsigned(T) = (is(typeof(T) == ubyte) || is(typeof(T) == ushort) | |
|| is(typeof(T) == uint) || is(T : ulong) || is(typeof(T) == lua_Unsigned)); | |
enum IsKContext(T) = (is(typeof(T) == ptrdiff_t) || is(typeof(T) == lua_KContext)); | |
enum IsString(T) = (is(T : string) || is(typeof(T) == const(char)*)); | |
enum IsFunction(T) = (is(T : lua_CFunction) || is(T : LuaStateFunction)); | |
Variant stackToVariant(LuaState state) { | |
return stackToVariant(state.state); | |
} | |
private Variant stackToVariant(lua_State* state) { | |
immutable(int) type = lua_type(state, -1); | |
Variant x; | |
switch (type) { | |
case (LUA_TBOOLEAN): | |
x = lua_toboolean(state, -1); | |
break; | |
case (LUA_TLIGHTUSERDATA): | |
x = lua_touserdata(state, -1); | |
break; | |
case (LUA_TNUMBER): | |
x = lua_tonumber(state, -1); | |
break; | |
case (LUA_TSTRING): | |
x = lua_tostring(state, -1).text; | |
break; | |
default: | |
lua_pop(state, 1); | |
break; | |
} | |
return x; | |
} | |
private void g_push(T)(lua_State* state, T value) { | |
static if (IsUnsigned!T) { | |
lua_pushinteger(state, cast(lua_Unsigned) value); | |
} else static if (IsInteger!T) { | |
lua_pushinteger(state, cast(lua_Integer) value); | |
} else static if (IsNumber!T) { | |
lua_pushnumber(state, cast(lua_Number) value); | |
} else static if (IsBoolean!T) { | |
lua_pushboolean(state, cast(int) value); | |
} else static if (IsString!T) { | |
lua_pushstring(state, toStringz(value)); | |
} else static if (IsFunction!T) { | |
lua_pushcfunction(state, cast(lua_CFunction) value); | |
} else { | |
lua_pushlightuserdata(state, value); | |
} | |
} | |
private T g_pop(T)(lua_State* state) { | |
static if (IsUnsigned!T) { | |
return cast(T) lua_tointeger(state, -1); | |
} else static if (IsInteger!T) { | |
return cast(T) lua_tointeger(state, -1); | |
} else static if (IsNumber!T) { | |
return cast(T) lua_tonumber(state, -1); | |
} else static if (IsBoolean!T) { | |
return cast(T) lua_toboolean(state, -1); | |
} else static if (IsString!T) { | |
return lua_tostring(state, -1).text; | |
} else static if (IsFunction!T) { | |
return lua_tocfunction(state.state, -1); | |
} else { | |
return cast(T) lua_touserdata(state, -1); | |
} | |
} | |
mixin template luaTableDef() { | |
lua_State* state; | |
void push(T)(T value) { | |
return g_push!T(state, value); | |
} | |
T pop(T)() { | |
return g_pop!T(state); | |
} | |
} | |
class LuaRegistry { | |
private: | |
mixin luaTableDef; | |
this(LuaState state) { | |
this(state.state); | |
} | |
this(lua_State* state) { | |
this.state = state; | |
} | |
public: | |
void set(T, TX)(TX id, T value) { | |
push!TX(id); | |
push!T(value); | |
lua_settable(state, LUA_REGISTRYINDEX); | |
} | |
T get(T, TX)(TX id) { | |
push!TX(id); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
return pop!T; | |
} | |
LuaTable newTable(string name) { | |
return new LuaTable(state, name, true, false); | |
} | |
} | |
/// A temporary table which will can set set in a function | |
/// You can only set values in this table, not get them. | |
class LuaLocalTable { | |
private: | |
mixin luaTableDef; | |
this(LuaState state) { | |
this(state.state); | |
} | |
this(lua_State* state) { | |
import std.stdio; | |
this.state = state; | |
// create table. | |
lua_newtable(this.state); | |
} | |
public: | |
void set(T, TX)(T id, TX value) { | |
import std.stdio; | |
//push!string(id.text); | |
push!TX(value); | |
lua_setfield(state, -2, toStringz(id.text)); | |
/*writeln("lua_settable"); | |
lua_settable(state, -3);*/ | |
} | |
void setTable(int id) { | |
lua_settable(state, id); | |
} | |
void setMetaTable(int id) { | |
lua_setmetatable(state, id); | |
} | |
void bindMetatable(LuaMetaTable table) { | |
table.setMetatable(); | |
} | |
} | |
class LuaMetaTable { | |
private: | |
mixin luaTableDef; | |
string name; | |
this(LuaState state, string name) { | |
this(state.state, name); | |
} | |
this(lua_State* state, string name) { | |
this.state = state; | |
this.name = name; | |
// create table. | |
luaL_newmetatable(this.state, toStringz(name)); | |
} | |
/// metatable shenannigans | |
void setMetatable(int id = -3) { | |
import std.stdio; | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
lua_setmetatable(state, id); | |
} | |
// pushes this to the lua stack | |
void toStack() { | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
public: | |
void set(T, TX)(T id, TX value) { | |
//push!T(id); | |
push!TX(value); | |
lua_setfield(state, -2, toStringz(id.text)); | |
//lua_settable(state, -3); | |
} | |
} | |
/// A lua table. | |
class LuaTable { | |
private: | |
mixin luaTableDef; | |
string name; | |
string isMetatableTo = null; | |
immutable(char)* nameRef; | |
bool parentRegistry; | |
this(lua_State* state) { | |
this.state = state; | |
} | |
this(lua_State* state, string name, bool parentRegistry = false, bool exists = false) { | |
this(state); | |
name = name; | |
nameRef = toStringz(name); | |
parentRegistry = parentRegistry; | |
if (!parentRegistry) { | |
// create table. | |
if (!exists) { | |
lua_newtable(this.state); | |
lua_setglobal(this.state, nameRef); | |
} else { | |
lua_getglobal(this.state, nameRef); | |
} | |
} else { | |
lua_newtable(this.state); | |
if (!exists) { | |
push!string(name); | |
lua_newtable(this.state); | |
lua_settable(state, LUA_REGISTRYINDEX); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
} | |
} | |
public: | |
~this() { | |
if (!parentRegistry) { | |
// push this table to the stack. | |
lua_getglobal(state, nameRef); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
lua_pushnil(state); | |
lua_settable(state, -2); | |
} | |
this(LuaState state, string name, bool exists = false) { | |
this(state.state, name, exists); | |
} | |
void set(T)(int id, T value) { | |
if (isMetatableTo !is null) { | |
throw new Exception("Please restore metatable before modifying"); | |
} | |
if (!parentRegistry) { | |
// push this table to the stack. | |
lua_getglobal(state, nameRef); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
lua_pushinteger(state, id); | |
push!T(value); | |
lua_settable(state, -3); | |
} | |
void set(T)(string name, T value) { | |
if (isMetatableTo !is null) { | |
throw new Exception("Please restore metatable before modifying"); | |
} | |
if (!parentRegistry) { | |
// push this table to the stack. | |
lua_getglobal(state, nameRef); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
lua_pushstring(state, toStringz(name)); | |
push!T(value); | |
lua_settable(state, -3); | |
} | |
T get(T)(int id) { | |
if (isMetatableTo !is null) { | |
throw new Exception("Please restore metatable before modifying"); | |
} | |
if (!parentRegistry) { | |
// push this table to the stack. | |
lua_getglobal(state, nameRef); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
// push value to stack and convert it. | |
lua_rawgeti(state, -1, id); | |
T p = pop!T(state); | |
// Pop value on stack and return. | |
lua_pop(state, 2); | |
return p; | |
} | |
T get(T)(string name) { | |
if (isMetatableTo !is null) { | |
throw new Exception("Please restore metatable before modifying"); | |
} | |
if (!parentRegistry) { | |
// push this table to the stack. | |
lua_getglobal(state, nameRef); | |
} else { | |
push!string(name); | |
lua_gettable(state, LUA_REGISTRYINDEX); | |
} | |
// push value to stack and convert it. | |
lua_getfield(state, -1, toStringz(name)); | |
T p = pop!T; | |
// Pop value on stack and return. | |
lua_pop(state, 2); | |
return p; | |
} | |
// pushes this to the lua stack | |
void toStack() { | |
lua_getglobal(state, nameRef); | |
} | |
/// Deletes this table from global space and sets it as metatable to another table | |
void metatableTo(LuaTable table) { | |
lua_getglobal(state, table.nameRef); | |
lua_getglobal(state, nameRef); | |
lua_setmetatable(state, -2); | |
lua_setglobal(state, table.nameRef); | |
// Remove old table ref | |
lua_pushnil(state); | |
lua_setglobal(state, nameRef); | |
isMetatableTo = table.name; | |
} | |
void bindMetatable(LuaMetaTable table) { | |
lua_getglobal(state, nameRef); | |
table.setMetatable(); | |
} | |
/// restores this table in to global scope. | |
void restoreThis() { | |
if (isMetatableTo !is null) { | |
throw new Exception("This table is not bound."); | |
} | |
lua_getglobal(state, toStringz(isMetatableTo)); | |
if (lua_getmetatable(state, -1) > 0) { | |
lua_setglobal(state, nameRef); | |
// Now remove the old table. | |
lua_getglobal(state, toStringz(isMetatableTo)); | |
lua_pushnil(state); | |
lua_setmetatable(state, -2); | |
isMetatableTo = null; | |
} else { | |
throw new Exception("Metatable seems to have been removed outside of scope."); | |
} | |
} | |
} | |
class LuaThread { | |
private: | |
lua_State* state; | |
LuaState parent; | |
this(LuaState parent) { | |
this.parent = parent; | |
} | |
public: | |
~this() { | |
lua_close(state); | |
} | |
/// Executes a string as lua code in the thread. | |
void executeString(string code, string name = "unnamed") { | |
if (luaL_dostring(state, toStringz(code)) != LUA_OK) { | |
throw new LuaException(lua_tostring(state, -1).text, name); | |
} | |
lua_close(state); | |
} | |
} | |
import polyplex.utils.strutils; | |
/// Makes object instantiable by Lua | |
/*template Luaify(T, int argCount) { | |
static if (is(T == struct)) { | |
import std.variant; | |
import std.traits; | |
import std.traits; | |
import std.format; | |
static T instantiate(Variant[] variant) { | |
T newT; | |
static foreach (element; __traits(derivedMembers, T)) { | |
static if (__traits(compiles, __traits(getMember, T, element))) { | |
static if (hasUDA!(__traits(getMember, T, element), Expose)) { | |
mixin(q{newT.%s = cast(typeof(__traits(getMember, T, element)))variant[%d];}.format(element, i)); | |
} | |
} | |
} | |
return newT; | |
} | |
} else { | |
} | |
}*/ | |
private mixin template luaUserData(T, string name = "") { | |
import std.stdio; | |
enum NAME = name != "" ? name : T.stringof; | |
private: | |
T* data; | |
public: | |
// TODO: Make this a template constraint? | |
LuaStateFunction __new = (state) { | |
// __index function | |
LuaStateFunction __index = (state) { | |
// Index. | |
string index = lua_tostring(state, -1).text; | |
if (index != "selfPtr") { | |
// get self pointer | |
lua_pushstring(state, toStringz("selfPtr")); | |
lua_rawget(state, 1); | |
// self pointer. | |
T* tPtr = (cast(T*)lua_touserdata(state, -1)); | |
// Trait dark magic hide your children. | |
static foreach (element; __traits(derivedMembers, T)) { | |
// Make sure only public members are considered | |
static if (__traits(compiles, __traits(getMember, T, element))) { | |
// They also need to have the @Expose attribute | |
static if (hasUDA!(__traits(getMember, T, element), Expose)) { | |
// And the the right index. | |
if (element == index) { | |
// Finally return it. | |
mixin(q{g_push!(typeof(__traits(getMember, T, element)))(state, tPtr.%s);}.format(element)); | |
return 1; | |
} | |
} | |
} | |
} | |
} | |
// return other things users might've set. | |
lua_pushstring(state, toStringz(index)); | |
lua_rawget(state, 1); | |
return 1; | |
}; | |
// __newindex function | |
LuaStateFunction __newindex = (state) { | |
// Get value | |
Variant val = stackToVariant(state); | |
// ! remember to pop first | |
lua_pop(state, 1); | |
// get index. | |
string index = stackToVariant(state).coerce!string; | |
if (index != "selfPtr") { | |
// Push string of self pointer. | |
lua_pushstring(state, toStringz("selfPtr")); | |
lua_rawget(state, 1); | |
T* tPtr = (cast(T*)lua_touserdata(state, -1)); | |
// Trait dark magic hide your children. | |
static foreach (element; __traits(derivedMembers, T)) { | |
// Make sure only public members are considered | |
static if (__traits(compiles, __traits(getMember, T, element))) { | |
// They also need to have the @Expose attribute | |
static if (hasUDA!(__traits(getMember, T, element), Expose)) { | |
// And the the right index. | |
if (element == index) { | |
// Finally change it. | |
mixin(q{tPtr.%s = val.coerce!%s;}.format(element, typeof(__traits(getMember, T, element)).stringof)); | |
return 0; | |
} | |
} | |
} | |
} | |
} | |
// WE DON'T WANT THE USER TO CHANGE THE SELFPTR. | |
return 0; | |
}; | |
// constructor begin | |
Variant[] params; | |
immutable(int) stack = lua_gettop(state); | |
foreach (i; 0 .. stack) { | |
params ~= stackToVariant(state); | |
} | |
lua_pop(state, stack); | |
// Instantiate T. | |
static if (is(T == struct)) { | |
T* tInstance = new T; | |
tInstance.instantiate(params); | |
} else { | |
T tInstance = new T; | |
tInstance.instantiate(params); | |
} | |
// Create local table. | |
LuaLocalTable table = new LuaLocalTable(state); | |
int mtabId = lua_gettop(state); | |
table.set!(string, T*)("selfPtr", tInstance); | |
LuaMetaTable metaTable = new LuaMetaTable(state, "A"); | |
int mttabId = lua_gettop(state); | |
metaTable.set!(string, LuaStateFunction)("__index", __index); | |
metaTable.set!(string, LuaStateFunction)("__newindex", __newindex); | |
metaTable.setMetatable(); | |
lua_pop(state, 1); | |
return lua_gettop(state); | |
}; | |
T get() { | |
return *data; | |
} | |
} | |
/// Binding to D data. | |
class ManagedUserData(T) { | |
private: | |
LuaState state; | |
LuaTable tableRef; | |
mixin luaUserData!T; | |
this()(LuaState state, T* dataPtr) { | |
// TODO: Do binding via template magic. | |
data = dataPtr; | |
state = state; | |
import std.stdio; | |
tableRef = state.newTable(T.stringof); | |
tableRef.set!LuaStateFunction("new", __new); | |
LuaTable table = state.newTable(T.stringof~"__callMeta"); | |
table.set!LuaStateFunction("__call", __new); | |
table.metatableTo(tableRef); | |
} | |
} | |
class LuaState { | |
private: | |
lua_State* state; | |
LuaRegistry registry; | |
LuaThread[] threads; | |
this(lua_State* state, bool wrapper = false) { | |
this.state = state; | |
if (!wrapper) | |
registry = new LuaRegistry(this); | |
} | |
public: | |
~this() { | |
destroy(threads); | |
destroy(registry); | |
lua_close(state); | |
} | |
/// Creates a binding to some user data (via pointer) | |
ManagedUserData!T bindUserData(T)(T* data) { | |
return new ManagedUserData!T(this, data); | |
} | |
/** | |
Creates a new table. | |
*/ | |
LuaTable newTable(string name) { | |
return new LuaTable(this, name); | |
} | |
/** | |
Creates a new table, which doesn't exist globally. | |
Values can only get set while the table is active. | |
Table becomes inactive as soon as other data is managed. | |
*/ | |
LuaLocalTable newLocalTable() { | |
return new LuaLocalTable(this); | |
} | |
/** | |
Creates a new callstack (called threads in lua) | |
This can be used cross-threads, be sure to use mutex when needed. | |
*/ | |
LuaThread newThread() { | |
LuaThread thread = new LuaThread(this); | |
threads ~= thread; | |
return thread; | |
} | |
/** | |
Execute a string. | |
*/ | |
void executeString(string code, string name = "unnamed", bool coroutine = false) { | |
if (coroutine) { | |
LuaThread t = newThread(); | |
t.executeString(code); | |
return; | |
} | |
if (luaL_dostring(state, toStringz(code)) != LUA_OK) { | |
throw new LuaException(lua_tostring(state, -1).text, name); | |
} | |
} | |
/** | |
Execute a file. | |
*/ | |
void executeFile(string path, bool coroutine = false) { | |
import std.file; | |
string data = path.readText; | |
executeString(data, path, coroutine); | |
} | |
ref LuaRegistry getRegistry() { | |
return registry; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment