├── .gitignore ├── LICENSE ├── README.md ├── action.go ├── demo ├── demo.go └── script.lua ├── dummy.go ├── extern.go ├── glua.c ├── glua.go ├── glua.h ├── go.mod ├── go.sum ├── pool.go ├── scheduler.go ├── utils.go ├── vm.go └── wapper.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ryou Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glua 2 | 3 | embed lua script language for Go 4 | 5 | go luajit glue,very very fast, support lua coroutine 6 | 7 | ## Demo 8 | easy use, like: 9 | ```go 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "time" 16 | 17 | "github.com/RyouZhang/go-lua" 18 | ) 19 | 20 | func main() { 21 | 22 | ts := time.Now() 23 | res, err := glua.NewAction().WithScript(` 24 | function fib(n) 25 | if n == 0 then 26 | return 0 27 | elseif n == 1 then 28 | return 1 29 | end 30 | return fib(n-1) + fib(n-2) 31 | end 32 | `).WithEntrypoint("fib").AddParam(35).Execute(context.Background()) 33 | 34 | fmt.Println("cost:", time.Now().Sub(ts)) 35 | fmt.Println(res, err) 36 | } 37 | 38 | ``` 39 | ## Benchmark 40 | 41 | | | fib(35) | fibt(35) | Language (Type) | 42 | | :--- | ---: | ---: | :---: | 43 | | [wassmer-go](https://github.com/wasmerio/wasmer-go) | `57ms` | `` | Wasm | 44 | | [**glua**](https://github.com/RyouZhang/go-lua) | `110ms` | `1.5ms` | Luajit(VM) | 45 | | [Tengo](https://github.com/d5/tengo) | `2,315ms` | `3ms` | Tengo (VM) | 46 | | [go-lua](https://github.com/Shopify/go-lua) | `4,028ms` | `3ms` | Lua (VM) | 47 | | [GopherLua](https://github.com/yuin/gopher-lua) | `4,409ms` | `3ms` | Lua (VM) | 48 | | [goja](https://github.com/dop251/goja) | `5,194ms` | `4ms` | JavaScript (VM) | 49 | | [starlark-go](https://github.com/google/starlark-go) | `6,954ms` | `3ms` | Starlark (Interpreter) | 50 | | [gpython](https://github.com/go-python/gpython) | `11,324ms` | `4ms` | Python (Interpreter) | 51 | | [Yaegi](https://github.com/containous/yaegi) | `4,715ms` | `10ms` | Yaegi (Interpreter) | 52 | | [otto](https://github.com/robertkrimen/otto) | `48,539ms` | `6ms` | JavaScript (Interpreter) | 53 | | [Anko](https://github.com/mattn/anko) | `52,821ms` | `6ms` | Anko (Interpreter) | 54 | | - | - | - | - | 55 | | Go | `47ms` | `2ms` | Go (Native) | 56 | | Lua | `756ms` | `2ms` | Lua (Native) | 57 | | Python | `1,907ms` | `14ms` | Python2 (Native) | 58 | 59 | _* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): 60 | Fibonacci(35)_ 61 | _* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): 62 | [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_ 63 | _* **Go** does not read the source code from file, while all other cases do_ 64 | -------------------------------------------------------------------------------- /action.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Action struct { 8 | script string 9 | scriptMD5 string 10 | scriptPath string 11 | entrypoint string 12 | params []interface{} 13 | funcs map[string]LuaExternFunc 14 | } 15 | 16 | func NewAction() *Action { 17 | return &Action{ 18 | params: make([]interface{}, 0), 19 | funcs: make(map[string]LuaExternFunc, 0), 20 | } 21 | } 22 | 23 | func (a *Action) WithScript(script string) *Action { 24 | a.script = script 25 | return a 26 | } 27 | 28 | func (a *Action) WithScriptMD5(md5 string) *Action { 29 | return a 30 | } 31 | 32 | func (a *Action) WithScriptPath(scriptPath string) *Action { 33 | a.scriptPath = scriptPath 34 | return a 35 | } 36 | 37 | func (a *Action) WithEntrypoint(entrypoint string) *Action { 38 | a.entrypoint = entrypoint 39 | return a 40 | } 41 | 42 | func (a *Action) AddParam(params ...interface{}) *Action { 43 | a.params = append(a.params, params...) 44 | return a 45 | } 46 | 47 | func (a *Action) AddFunc(methodName string, method LuaExternFunc) *Action { 48 | a.funcs[methodName] = method 49 | return a 50 | } 51 | 52 | func (a *Action) Execute(ctx context.Context) (interface{}, error) { 53 | return getScheduler().do(ctx, a) 54 | } 55 | -------------------------------------------------------------------------------- /demo/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | glua "github.com/RyouZhang/go-lua" 11 | ) 12 | 13 | func test_sum(ctx context.Context, args ...interface{}) (interface{}, error) { 14 | sum := 0 15 | for _, arg := range args { 16 | sum = sum + int(arg.(int64)) 17 | } 18 | if sum%2 == 0 { 19 | return sum, nil 20 | } else { 21 | return nil, fmt.Errorf("bad sum") 22 | } 23 | } 24 | 25 | func json_decode(ctx context.Context, args ...interface{}) (interface{}, error) { 26 | raw := args[0].(string) 27 | 28 | var res map[string]interface{} 29 | err := json.Unmarshal([]byte(raw), &res) 30 | return []any{res, args[1]}, err 31 | } 32 | 33 | func get_header_field(ctx context.Context, args ...interface{}) (interface{}, error) { 34 | req := args[0].(*http.Request) 35 | key := args[1].(string) 36 | return req.Header.Get(key), nil 37 | } 38 | 39 | type A interface { 40 | Name() string 41 | } 42 | 43 | type AA struct { 44 | } 45 | 46 | func (a *AA) Name() string { 47 | return "hello world" 48 | } 49 | 50 | func main() { 51 | 52 | glua.RegisterExternMethod("json_decode", json_decode) 53 | glua.RegisterExternMethod("test_sum", test_sum) 54 | glua.RegisterExternMethod("get_header_field", get_header_field) 55 | 56 | s := time.Now() 57 | res, err := glua.NewAction().WithScript(` 58 | function fib(n) 59 | if n == 0 then 60 | return 0 61 | elseif n == 1 then 62 | return 1 63 | end 64 | return fib(n-1) + fib(n-2) 65 | end 66 | `).WithEntrypoint("fib").AddParam(35).Execute(context.Background()) 67 | fmt.Println(time.Now().Sub(s)) 68 | fmt.Println(res, err) 69 | 70 | s = time.Now() 71 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("fib").AddParam(35).Execute(context.Background()) 72 | fmt.Println(time.Now().Sub(s)) 73 | fmt.Println(res, err) 74 | 75 | s = time.Now() 76 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("fibt").AddParam(35).Execute(context.Background()) 77 | fmt.Println(time.Now().Sub(s)) 78 | fmt.Println(res, err) 79 | 80 | s = time.Now() 81 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("test_args").AddParam([]interface{}{69, 56}).Execute(context.Background()) 82 | fmt.Println(time.Now().Sub(s)) 83 | fmt.Println(res, err) 84 | 85 | fmt.Println("======") 86 | s = time.Now() 87 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("async_json_encode").Execute(context.Background()) 88 | fmt.Println(time.Now().Sub(s)) 89 | fmt.Println(res, err) 90 | fmt.Println("======") 91 | 92 | s = time.Now() 93 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("test_pull_table").AddParam(69).Execute(context.Background()) 94 | fmt.Println(time.Now().Sub(s)) 95 | fmt.Println(res, err) 96 | 97 | req, _ := http.NewRequest("GET", "https://www.bing.com", nil) 98 | req.Header.Add("test", "3121232") 99 | 100 | var a A 101 | a = &AA{} 102 | 103 | res, err = glua.NewAction().WithScriptPath("script.lua").WithEntrypoint("test").AddParam(req, a).Execute(context.Background()) 104 | fmt.Println(time.Now().Sub(s)) 105 | fmt.Println(res, err) 106 | } 107 | -------------------------------------------------------------------------------- /demo/script.lua: -------------------------------------------------------------------------------- 1 | 2 | function fib(n) 3 | if n == 0 then 4 | return 0 5 | elseif n == 1 then 6 | return 1 7 | end 8 | return fib(n-1) + fib(n-2) 9 | end 10 | 11 | function fibt(n) 12 | return fibc(n, 0, 1) 13 | end 14 | 15 | function fibc(n, a, b) 16 | if n == 0 then 17 | return a 18 | else 19 | if n == 1 then return b end 20 | end 21 | return fibc(n-1, b, a+b) 22 | end 23 | 24 | function test_args(n) 25 | res, err = sync_extern_method('test_sum', 1,2,3,4,5,6,n[1],n[2]) 26 | if err == nil then 27 | return res 28 | else 29 | error(err) 30 | end 31 | end 32 | 33 | function test_pull_table(obj) 34 | return {a=true, b=123, c='hello luajit', d={e=12, f='good golang'}, e={1,2,3,4,4}, 1, m=obj}, nil 35 | end 36 | 37 | function async_json_encode() 38 | local res, err = coroutine.yield('json_decode', '{"a":"ads","b":12,"c":"sadh"}', 'hello world') 39 | if err ~= nil then 40 | return nil, nil, err 41 | end 42 | return unpack(res) 43 | end 44 | 45 | 46 | function test(req, rw) 47 | local name, _ = sync_extern_method('get_header_field', req, 'test') 48 | return 'hello world' .. name, name 49 | end -------------------------------------------------------------------------------- /dummy.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | "unsafe" 9 | ) 10 | 11 | // #cgo CFLAGS: -I/usr/local/include/luajit-2.1 12 | // #cgo LDFLAGS: -L/usr/local/lib -lluajit -ldl -lm 13 | //#include "glua.h" 14 | import "C" 15 | 16 | type dummy struct { 17 | key []byte 18 | val interface{} 19 | } 20 | 21 | var ( 22 | dummyCache map[uintptr]map[uintptr]*dummy 23 | dummyRW sync.RWMutex 24 | ) 25 | 26 | func init() { 27 | dummyCache = make(map[uintptr]map[uintptr]*dummy) 28 | } 29 | 30 | // lua dummy method 31 | func pushDummy(vm *C.struct_lua_State, obj interface{}) unsafe.Pointer { 32 | vmKey := generateLuaStateId(vm) 33 | 34 | val := reflect.ValueOf(obj) 35 | var ( 36 | realObj interface{} 37 | dummyId uintptr 38 | ) 39 | 40 | switch val.Kind() { 41 | case reflect.Pointer: 42 | { 43 | realObj = val.Elem().Interface() 44 | } 45 | default: 46 | { 47 | realObj = obj 48 | } 49 | } 50 | 51 | dObj := &dummy{ 52 | key: []byte(fmt.Sprintf("%p", &realObj)), 53 | val: obj, 54 | } 55 | 56 | dummyId = uintptr(unsafe.Pointer(&(dObj.key[0]))) 57 | 58 | dummyRW.Lock() 59 | target, ok := dummyCache[vmKey] 60 | if false == ok { 61 | target = make(map[uintptr]*dummy) 62 | target[dummyId] = dObj 63 | dummyCache[vmKey] = target 64 | } else { 65 | target[dummyId] = dObj 66 | } 67 | dummyRW.Unlock() 68 | 69 | return unsafe.Pointer(dummyId) 70 | } 71 | 72 | func findDummy(vm *C.struct_lua_State, ptr unsafe.Pointer) (interface{}, error) { 73 | vmKey := generateLuaStateId(vm) 74 | dummyId := uintptr(ptr) 75 | 76 | dummyRW.RLock() 77 | defer dummyRW.RUnlock() 78 | 79 | target, ok := dummyCache[vmKey] 80 | if false == ok { 81 | return nil, errors.New("Invalid VMKey") 82 | } 83 | dObj, ok := target[dummyId] 84 | if false == ok { 85 | return nil, errors.New("Invalid DummyId") 86 | } 87 | return dObj.val, nil 88 | } 89 | 90 | func cleanDummy(vm *C.struct_lua_State) { 91 | vmKey := generateLuaStateId(vm) 92 | 93 | dummyRW.Lock() 94 | defer dummyRW.Unlock() 95 | delete(dummyCache, vmKey) 96 | } 97 | -------------------------------------------------------------------------------- /extern.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "sync" 7 | ) 8 | 9 | // #cgo CFLAGS: -I/usr/local/include/luajit-2.1 10 | // #cgo LDFLAGS: -L/usr/local/lib -lluajit -ldl -lm 11 | //#include "glua.h" 12 | import "C" 13 | 14 | var ( 15 | methodMu sync.RWMutex 16 | methodDic map[string]LuaExternFunc 17 | ) 18 | 19 | type LuaExternFunc func(context.Context, ...interface{}) (interface{}, error) 20 | 21 | func init() { 22 | methodDic = make(map[string]LuaExternFunc) 23 | } 24 | 25 | func RegisterExternMethod(methodName string, method LuaExternFunc) error { 26 | methodMu.Lock() 27 | defer methodMu.Unlock() 28 | _, ok := methodDic[methodName] 29 | if ok { 30 | return errors.New("Duplicate Method Name") 31 | } 32 | methodDic[methodName] = method 33 | return nil 34 | } 35 | 36 | func callExternMethod(ctx context.Context, methodName string, args ...interface{}) (interface{}, error) { 37 | methodMu.RLock() 38 | defer methodMu.RUnlock() 39 | tagetMethod, ok := methodDic[methodName] 40 | if false == ok { 41 | return nil, errors.New("Invalid Method Name") 42 | } 43 | return tagetMethod(ctx, args...) 44 | } 45 | 46 | //export sync_extern_method 47 | func sync_extern_method(vm *C.struct_lua_State) C.int { 48 | count := int(C.glua_gettop(vm)) 49 | args := make([]interface{}, count) 50 | for { 51 | count = int(C.glua_gettop(vm)) 52 | if count == 0 { 53 | break 54 | } 55 | args[count-1] = pullFromLua(vm, -1) 56 | C.glua_pop(vm, 1) 57 | } 58 | methodName := args[0].(string) 59 | if len(args) > 1 { 60 | args = args[1:] 61 | } else { 62 | args = make([]interface{}, 0) 63 | } 64 | 65 | tagetMethod, ok := methodDic[methodName] 66 | if false == ok { 67 | C.glua_pushnil(vm) 68 | str := "Invalid Method Name" 69 | C.glua_pushlstring(vm, C.CString(str), C.size_t(len([]byte(str)))) 70 | return 2 71 | } 72 | threadId := generateLuaStateId(vm) 73 | ctx := findThreadContext(threadId) 74 | 75 | res, err := tagetMethod(ctx, args...) 76 | if err != nil { 77 | pushToLua(vm, 0) 78 | str := err.Error() 79 | C.glua_pushlstring(vm, C.CString(str), C.size_t(len([]byte(str)))) 80 | return 2 81 | } else { 82 | pushToLua(vm, res) 83 | C.glua_pushnil(vm) 84 | return 2 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /glua.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "_cgo_export.h" 9 | 10 | extern int sync_extern_method(lua_State* _L); 11 | 12 | int gluaL_dostring(lua_State* _L, char* script) { 13 | int res = luaL_dostring(_L, script); 14 | free(script); 15 | return res; 16 | } 17 | void glua_getglobal(lua_State* _L, char* name) { 18 | lua_getglobal(_L, name); 19 | free(name); 20 | } 21 | void glua_setglobal(lua_State* _L, char* name) { 22 | lua_setglobal(_L, name); 23 | free(name); 24 | } 25 | void glua_pushlightuserdata(lua_State* _L, void* obj) { 26 | lua_pushlightuserdata(_L, obj); 27 | } 28 | int glua_pcall(lua_State* _L, int args, int results) { 29 | return lua_pcall(_L, args, results, 0); 30 | } 31 | lua_Number glua_tonumber(lua_State* _L, int index) { 32 | return lua_tonumber(_L, index); 33 | } 34 | int glua_yield(lua_State *_L, int nresults) { 35 | return lua_yield(_L, nresults); 36 | } 37 | const char* glua_tostring(lua_State* _L, int index) { 38 | return lua_tostring(_L, index); 39 | } 40 | void glua_pop(lua_State* _L, int num) { 41 | lua_pop(_L, num); 42 | } 43 | lua_State *glua_tothread(lua_State* _L, int index) { 44 | return lua_tothread(_L, index); 45 | } 46 | 47 | int glua_istable(lua_State* _L, int index) { 48 | return lua_istable(_L, index); 49 | } 50 | void* glua_touserdata(lua_State* _L, int index) { 51 | return lua_touserdata(_L, index); 52 | } 53 | 54 | int glua_resume (lua_State *_L, int narg) { 55 | return lua_resume(_L, narg); 56 | } 57 | 58 | int glua_gettop(lua_State *_L) { 59 | return lua_gettop(_L); 60 | } 61 | 62 | int glua_gc (lua_State *_L, int what, int data) { 63 | return lua_gc(_L, what, data); 64 | } 65 | 66 | lua_State *gluaL_newstate (void) { 67 | return luaL_newstate(); 68 | } 69 | 70 | void gluaL_openlibs (lua_State *_L) { 71 | luaL_openlibs(_L); 72 | } 73 | 74 | lua_State *glua_newthread (lua_State *_L) { 75 | return lua_newthread(_L); 76 | } 77 | 78 | void glua_close (lua_State *_L) { 79 | lua_close(_L); 80 | } 81 | 82 | void glua_remove (lua_State *_L, int index) { 83 | lua_remove(_L, index); 84 | } 85 | 86 | int glua_type (lua_State *_L, int index) { 87 | return lua_type(_L, index); 88 | } 89 | 90 | void glua_pushlstring (lua_State *_L, char *s, size_t len) { 91 | lua_pushlstring (_L, s, len); 92 | free(s); 93 | } 94 | 95 | void glua_pushnumber (lua_State *_L, lua_Number n) { 96 | lua_pushnumber(_L, n); 97 | } 98 | 99 | void glua_pushboolean (lua_State *_L, int b) { 100 | lua_pushboolean(_L, b); 101 | } 102 | 103 | void glua_pushnil (lua_State *_L) { 104 | lua_pushnil(_L); 105 | } 106 | 107 | void glua_createtable (lua_State *_L, int narr, int nrec) { 108 | lua_createtable(_L, narr, nrec); 109 | } 110 | 111 | void glua_settable (lua_State *_L, int index) { 112 | lua_settable (_L, index); 113 | } 114 | 115 | int glua_next (lua_State *_L, int index) { 116 | return lua_next(_L, index); 117 | } 118 | 119 | int glua_toboolean (lua_State *_L, int index) { 120 | return lua_toboolean(_L, index); 121 | } 122 | 123 | void register_go_method(lua_State* _L) { 124 | lua_pushcfunction(_L, &sync_extern_method); 125 | lua_setglobal(_L, "sync_extern_method"); 126 | } -------------------------------------------------------------------------------- /glua.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var ( 8 | globalOpts *Options 9 | locker sync.Mutex 10 | ) 11 | 12 | func init() { 13 | globalOpts = NewOptions() 14 | } 15 | 16 | type Metric interface { 17 | Counter(name string, value int64, labels map[string]string) 18 | Gauge(name string, value int64, labels map[string]string) 19 | } 20 | 21 | type Options struct { 22 | maxVmSize int 23 | preloadScriptMethod func() string 24 | metricHandle Metric 25 | } 26 | 27 | func NewOptions() *Options { 28 | return &Options{ 29 | maxVmSize: 4, 30 | } 31 | } 32 | 33 | func (opt *Options) WithMaxVMSize(maxVmSize int) *Options { 34 | opt.maxVmSize = maxVmSize 35 | return opt 36 | } 37 | 38 | func (opt *Options) SetPreloadScripeMethod(method func() string) *Options { 39 | opt.preloadScriptMethod = method 40 | return opt 41 | } 42 | 43 | func (opt *Options) SetMetric(handle Metric) *Options { 44 | opt.metricHandle = handle 45 | return opt 46 | } 47 | 48 | func GlobalOptions(opts *Options) { 49 | locker.Lock() 50 | defer locker.Unlock() 51 | globalOpts = opts 52 | } 53 | 54 | // metric 55 | func metricCounter(name string, value int64, labels map[string]string) { 56 | if globalOpts.metricHandle != nil { 57 | globalOpts.metricHandle.Counter(name, value, labels) 58 | } 59 | } 60 | 61 | func metricGauge(name string, value int64, labels map[string]string) { 62 | if globalOpts.metricHandle != nil { 63 | globalOpts.metricHandle.Gauge(name, value, labels) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /glua.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int gluaL_dostring(lua_State* _L, char* script); 7 | void glua_getglobal(lua_State* _L, char* name); 8 | void glua_setglobal(lua_State* _L, char* name); 9 | 10 | void glua_pushlightuserdata(lua_State* _L, void* obj); 11 | int glua_pcall(lua_State* _L, int args, int results); 12 | lua_Number glua_tonumber(lua_State* _L, int index); 13 | int glua_yield(lua_State *_L, int nresults); 14 | const char* glua_tostring(lua_State* _L, int index); 15 | void glua_pop(lua_State* _L, int num); 16 | lua_State *glua_tothread(lua_State* _L, int index); 17 | lua_State *gluaL_newstate (void); 18 | int glua_istable(lua_State* _L, int index); 19 | void* glua_touserdata(lua_State* _L, int index); 20 | int glua_resume (lua_State *_L, int narg); 21 | int glua_gettop(lua_State *_L); 22 | int glua_gc (lua_State *_L, int what, int data); 23 | void gluaL_openlibs (lua_State *_L); 24 | lua_State *glua_newthread (lua_State *_L); 25 | void glua_close (lua_State *_L); 26 | void glua_remove (lua_State *_L, int index); 27 | int glua_type (lua_State *_L, int index); 28 | 29 | void glua_pushlstring (lua_State *_L, char *s, size_t len); 30 | void glua_pushnumber (lua_State *_L, lua_Number n); 31 | void glua_pushboolean (lua_State *_L, int b); 32 | void glua_pushnil (lua_State *_L); 33 | 34 | void glua_createtable (lua_State *_L, int narr, int nrec); 35 | void glua_settable (lua_State *_L, int index); 36 | 37 | int glua_next (lua_State *_L, int index); 38 | int glua_toboolean (lua_State *_L, int index); 39 | 40 | //for go extra 41 | void register_go_method(lua_State* _L); -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/RyouZhang/go-lua 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyouZhang/go-lua/966b1bf915f04d0799f0b7280a9a15c8d8465e09/go.sum -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | type vmPool struct { 4 | maxVmCount int 5 | vmQueue []*luaVm 6 | idleVmDic map[uintptr]*luaVm 7 | inUseVmDic map[uintptr]*luaVm 8 | } 9 | 10 | func newVMPool(maxVmCount int) *vmPool { 11 | if maxVmCount < 0 { 12 | maxVmCount = 4 13 | } 14 | if maxVmCount > 16 { 15 | maxVmCount = 16 16 | } 17 | return &vmPool{ 18 | maxVmCount: maxVmCount, 19 | vmQueue: make([]*luaVm, 0), 20 | inUseVmDic: make(map[uintptr]*luaVm), 21 | idleVmDic: make(map[uintptr]*luaVm), 22 | } 23 | } 24 | 25 | func (vp *vmPool) accquire() *luaVm { 26 | defer func() { 27 | metricGauge("glua_vm_idle_count", int64(len(vp.idleVmDic)), nil) 28 | metricGauge("glua_vm_inuse_count", int64(len(vp.inUseVmDic)), nil) 29 | }() 30 | 31 | // check idle vm 32 | if len(vp.vmQueue) > 0 { 33 | vm := vp.vmQueue[0] 34 | if len(vp.vmQueue) == 1 { 35 | vp.vmQueue = []*luaVm{} 36 | } else { 37 | vp.vmQueue = vp.vmQueue[1:] 38 | } 39 | delete(vp.idleVmDic, vm.stateId) 40 | vp.inUseVmDic[vm.stateId] = vm 41 | return vm 42 | } 43 | 44 | // create new vm 45 | if len(vp.inUseVmDic) == vp.maxVmCount { 46 | return nil 47 | } 48 | vm := newLuaVm() 49 | vp.inUseVmDic[vm.stateId] = vm 50 | return vm 51 | } 52 | 53 | func (vp *vmPool) release(vm *luaVm) { 54 | defer func() { 55 | metricGauge("glua_vm_idle_count", int64(len(vp.idleVmDic)), nil) 56 | metricGauge("glua_vm_inuse_count", int64(len(vp.inUseVmDic)), nil) 57 | }() 58 | delete(vp.inUseVmDic, vm.stateId) 59 | if vm.needDestory && vm.resumeCount == 0 { 60 | vm.destory() 61 | } else { 62 | vp.idleVmDic[vm.stateId] = vm 63 | vp.vmQueue = append(vp.vmQueue, vm) 64 | } 65 | } 66 | 67 | func (vp *vmPool) find(stateId uintptr) *luaVm { 68 | defer func() { 69 | metricGauge("glua_vm_idle_count", int64(len(vp.idleVmDic)), nil) 70 | metricGauge("glua_vm_inuse_count", int64(len(vp.inUseVmDic)), nil) 71 | }() 72 | 73 | vm, ok := vp.idleVmDic[stateId] 74 | if !ok { 75 | return nil 76 | } 77 | 78 | vp.inUseVmDic[vm.stateId] = vm 79 | delete(vp.idleVmDic, stateId) 80 | 81 | index := 0 82 | for index, _ = range vp.vmQueue { 83 | if vp.vmQueue[index].stateId == vm.stateId { 84 | break 85 | } 86 | } 87 | 88 | switch { 89 | case len(vp.vmQueue) == 1: 90 | vp.vmQueue = []*luaVm{} 91 | case index == len(vp.vmQueue)-1: 92 | vp.vmQueue = vp.vmQueue[:index] 93 | case index == 0: 94 | vp.vmQueue = vp.vmQueue[1:] 95 | default: 96 | vp.vmQueue = append(vp.vmQueue[:index], vp.vmQueue[index+1:]...) 97 | } 98 | return vm 99 | } 100 | -------------------------------------------------------------------------------- /scheduler.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | var ( 12 | scheudlerOnce sync.Once 13 | schuelder *vmScheduler 14 | ) 15 | 16 | type luaContext struct { 17 | ctx context.Context 18 | act *Action 19 | luaStateId uintptr 20 | luaThreadId uintptr 21 | callback chan interface{} 22 | status int //0 wating, 1 running, 2 yield, 3 finish 23 | } 24 | 25 | type vmScheduler struct { 26 | shutdown chan bool 27 | resumes []*luaContext 28 | waitings []*luaContext 29 | luaCtxQueue chan *luaContext 30 | vmQueue chan *luaVm 31 | vp *vmPool 32 | } 33 | 34 | func getScheduler() *vmScheduler { 35 | scheudlerOnce.Do(func() { 36 | schuelder = &vmScheduler{ 37 | shutdown: make(chan bool), 38 | resumes: make([]*luaContext, 0), 39 | waitings: make([]*luaContext, 0), 40 | luaCtxQueue: make(chan *luaContext, 16), 41 | vmQueue: make(chan *luaVm, 16), 42 | vp: newVMPool(globalOpts.maxVmSize), 43 | } 44 | go schuelder.loop() 45 | }) 46 | return schuelder 47 | } 48 | 49 | func (s *vmScheduler) loop() { 50 | for { 51 | metricGauge("glua_sheduler_resume_queue_size", int64(len(s.resumes)), nil) 52 | metricGauge("glua_sheduler_waiting_queue_size", int64(len(s.waitings)), nil) 53 | select { 54 | case <-s.shutdown: 55 | { 56 | return 57 | } 58 | case vm := <-s.vmQueue: 59 | { 60 | luaCtx := s.pick(vm.stateId) 61 | if luaCtx == nil { 62 | s.vp.release(vm) 63 | continue 64 | } 65 | go s.run(vm, luaCtx) 66 | } 67 | case luaCtx := <-s.luaCtxQueue: 68 | { 69 | switch luaCtx.status { 70 | case 0: 71 | { 72 | vm := s.vp.accquire() 73 | if vm == nil { 74 | s.waitings = append(s.waitings, luaCtx) 75 | continue 76 | } else { 77 | luaCtx.status = 1 78 | go s.run(vm, luaCtx) 79 | } 80 | } 81 | case 2: 82 | { 83 | vm := s.vp.find(luaCtx.luaStateId) 84 | if vm == nil { 85 | s.resumes = append(s.resumes, luaCtx) 86 | continue 87 | } 88 | go s.run(vm, luaCtx) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | func (s *vmScheduler) run(vm *luaVm, luaCtx *luaContext) { 97 | defer func() { 98 | if e := recover(); e != nil { 99 | err, ok := e.(error) 100 | if !ok { 101 | err = errors.New(fmt.Sprintf("%v", e)) 102 | } 103 | luaCtx.callback <- err 104 | } 105 | s.vmQueue <- vm 106 | }() 107 | 108 | switch luaCtx.status { 109 | case 2: 110 | vm.resume(luaCtx.ctx, luaCtx) 111 | default: 112 | vm.run(luaCtx.ctx, luaCtx) 113 | } 114 | } 115 | 116 | func (s *vmScheduler) pick(stateId uintptr) *luaContext { 117 | var ( 118 | index int 119 | luaCtx *luaContext 120 | ) 121 | // check resume list 122 | for index, _ = range s.resumes { 123 | if s.resumes[index].luaStateId == stateId { 124 | luaCtx = s.resumes[index] 125 | break 126 | } 127 | } 128 | if luaCtx != nil { 129 | switch { 130 | case len(s.resumes) == 1: 131 | s.resumes = []*luaContext{} 132 | case index == len(s.resumes)-1: 133 | s.resumes = s.resumes[:index] 134 | case index == 0: 135 | s.resumes = s.resumes[1:] 136 | default: 137 | s.resumes = append(s.resumes[:index], s.resumes[index+1:]...) 138 | } 139 | return luaCtx 140 | } 141 | // check waitings list 142 | if len(s.waitings) == 0 { 143 | return nil 144 | } 145 | luaCtx = s.waitings[0] 146 | switch { 147 | case len(s.waitings) == 1: 148 | s.waitings = []*luaContext{} 149 | default: 150 | s.waitings = s.waitings[1:] 151 | } 152 | return luaCtx 153 | } 154 | 155 | func (s *vmScheduler) do(ctx context.Context, act *Action) (interface{}, error) { 156 | ts := time.Now() 157 | defer func() { 158 | metricCounter("glua_action_scheduler_total", 1, nil) 159 | metricCounter("glua_action_scheduler_second_total", int64(time.Now().Sub(ts).Milliseconds()), nil) 160 | }() 161 | 162 | luaCtx := &luaContext{ 163 | ctx: ctx, 164 | act: act, 165 | luaStateId: 0, 166 | luaThreadId: 0, 167 | callback: make(chan interface{}, 1), 168 | status: 0, 169 | } 170 | 171 | s.luaCtxQueue <- luaCtx 172 | 173 | res := <-luaCtx.callback 174 | switch res.(type) { 175 | case error: 176 | return nil, res.(error) 177 | default: 178 | return res, nil 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | // "fmt" 5 | // "strconv" 6 | "context" 7 | "sync" 8 | "unsafe" 9 | ) 10 | 11 | // #cgo CFLAGS: -I/usr/local/include/luajit-2.1 12 | // #cgo LDFLAGS: -L/usr/local/lib -lluajit -ldl -lm 13 | //#include "glua.h" 14 | import "C" 15 | 16 | var ( 17 | threadCtxDic map[uintptr]context.Context 18 | threadCtxDicMutex sync.RWMutex 19 | ) 20 | 21 | func init() { 22 | threadCtxDic = make(map[uintptr]context.Context) 23 | } 24 | 25 | func generateLuaStateId(vm *C.struct_lua_State) uintptr { 26 | return uintptr(unsafe.Pointer(vm)) 27 | } 28 | 29 | func createLuaState() (uintptr, *C.struct_lua_State) { 30 | vm := C.gluaL_newstate() 31 | C.glua_gc(vm, C.LUA_GCSTOP, 0) 32 | C.gluaL_openlibs(vm) 33 | C.glua_gc(vm, C.LUA_GCRESTART, 0) 34 | C.register_go_method(vm) 35 | 36 | if globalOpts.preloadScriptMethod != nil { 37 | script := globalOpts.preloadScriptMethod() 38 | C.gluaL_dostring(vm, C.CString(script)) 39 | } 40 | 41 | return generateLuaStateId(vm), vm 42 | } 43 | 44 | func createLuaThread(vm *C.struct_lua_State) (uintptr, *C.struct_lua_State) { 45 | L := C.glua_newthread(vm) 46 | return generateLuaStateId(L), L 47 | } 48 | 49 | func pushThreadContext(threadId uintptr, ctx context.Context) { 50 | threadCtxDicMutex.Lock() 51 | defer threadCtxDicMutex.Unlock() 52 | threadCtxDic[threadId] = ctx 53 | } 54 | 55 | func popThreadContext(threadId uintptr) { 56 | threadCtxDicMutex.Lock() 57 | defer threadCtxDicMutex.Unlock() 58 | delete(threadCtxDic, threadId) 59 | } 60 | 61 | func findThreadContext(threadId uintptr) context.Context { 62 | threadCtxDicMutex.RLock() 63 | defer threadCtxDicMutex.RUnlock() 64 | return threadCtxDic[threadId] 65 | } 66 | -------------------------------------------------------------------------------- /vm.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "context" 5 | "crypto/md5" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | ) 10 | 11 | // #cgo CFLAGS: -I/usr/local/include/luajit-2.1 12 | // #cgo LDFLAGS: -L/usr/local/lib -lluajit -ldl -lm 13 | //#include "glua.h" 14 | import "C" 15 | 16 | type luaVm struct { 17 | stateId uintptr 18 | state *C.struct_lua_State 19 | scriptMD5Dic map[string]bool 20 | resumeCount int 21 | needDestory bool 22 | threadDic map[uintptr]*C.struct_lua_State 23 | } 24 | 25 | func newLuaVm() *luaVm { 26 | stateId, state := createLuaState() 27 | return &luaVm{ 28 | stateId: stateId, 29 | state: state, 30 | resumeCount: 0, 31 | needDestory: false, 32 | scriptMD5Dic: make(map[string]bool), 33 | threadDic: make(map[uintptr]*C.struct_lua_State), 34 | } 35 | } 36 | 37 | func (v *luaVm) run(ctx context.Context, luaCtx *luaContext) { 38 | metricCounter("glua_vm_run_total", 1, map[string]string{ 39 | "vm_id": fmt.Sprintf("%d", v.stateId), 40 | }) 41 | metricGauge("glua_vm_memory_size", int64(C.glua_gc(v.state, C.LUA_GCCOUNT, 0)<<10+C.glua_gc(v.state, C.LUA_GCCOUNTB, 0)), map[string]string{ 42 | "vm_id": fmt.Sprintf("%d", v.stateId), 43 | }) 44 | 45 | defer func() { 46 | C.glua_gc(v.state, C.LUA_GCCOLLECT, 0) 47 | }() 48 | 49 | threadId, L := createLuaThread(v.state) 50 | 51 | v.threadDic[threadId] = L 52 | 53 | luaCtx.luaStateId = v.stateId 54 | luaCtx.luaThreadId = threadId 55 | pushThreadContext(threadId, luaCtx.ctx) 56 | 57 | ret := C.int(C.LUA_OK) 58 | 59 | if len(luaCtx.act.script) > 0 { 60 | if len(luaCtx.act.entrypoint) > 0 { 61 | if len(luaCtx.act.scriptMD5) > 0 { 62 | if _, ok := v.scriptMD5Dic[luaCtx.act.scriptMD5]; !ok { 63 | v.scriptMD5Dic[luaCtx.act.scriptMD5] = true 64 | ret = C.gluaL_dostring(L, C.CString(luaCtx.act.script)) 65 | } 66 | } else { 67 | scriptMD5 := fmt.Sprintf("%x", md5.Sum([]byte(luaCtx.act.script))) 68 | if _, ok := v.scriptMD5Dic[scriptMD5]; !ok { 69 | v.scriptMD5Dic[scriptMD5] = true 70 | ret = C.gluaL_dostring(L, C.CString(luaCtx.act.script)) 71 | } 72 | } 73 | } else { 74 | ret = C.gluaL_dostring(L, C.CString(luaCtx.act.script)) 75 | } 76 | } else { 77 | raw, err := ioutil.ReadFile(luaCtx.act.scriptPath) 78 | if err != nil { 79 | luaCtx.callback <- errors.New(C.GoString(C.glua_tostring(L, -1))) 80 | close(luaCtx.callback) 81 | v.destoryThread(threadId, L) 82 | return 83 | } 84 | if len(luaCtx.act.entrypoint) > 0 { 85 | scriptMD5 := fmt.Sprintf("%x", md5.Sum(raw)) 86 | if _, ok := v.scriptMD5Dic[scriptMD5]; !ok { 87 | v.scriptMD5Dic[scriptMD5] = true 88 | ret = C.gluaL_dostring(L, C.CString(string(raw))) 89 | } 90 | } else { 91 | ret = C.gluaL_dostring(L, C.CString(string(raw))) 92 | } 93 | } 94 | 95 | if ret == C.LUA_OK && len(luaCtx.act.entrypoint) > 0 { 96 | C.glua_getglobal(L, C.CString(luaCtx.act.entrypoint)) 97 | pushToLua(L, luaCtx.act.params...) 98 | ret = C.glua_resume(L, C.int(len(luaCtx.act.params))) 99 | } 100 | 101 | switch ret { 102 | case C.LUA_OK: 103 | { 104 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "success"}) 105 | luaCtx.status = 3 106 | count := int(C.glua_gettop(L)) 107 | res := make([]interface{}, count) 108 | for { 109 | count = int(C.glua_gettop(L)) 110 | if count == 0 { 111 | break 112 | } 113 | res[count-1] = pullFromLua(L, -1) 114 | C.glua_pop(L, 1) 115 | } 116 | switch len(res) { 117 | case 0: 118 | luaCtx.callback <- nil 119 | case 1: 120 | luaCtx.callback <- res[0] 121 | default: 122 | luaCtx.callback <- res 123 | } 124 | close(luaCtx.callback) 125 | v.destoryThread(threadId, L) 126 | } 127 | case C.LUA_YIELD: 128 | { 129 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "yield"}) 130 | luaCtx.status = 2 131 | v.resumeCount++ 132 | 133 | count := int(C.glua_gettop(L)) 134 | args := make([]interface{}, count) 135 | for { 136 | count = int(C.glua_gettop(L)) 137 | if count == 0 { 138 | break 139 | } 140 | args[count-1] = pullFromLua(L, -1) 141 | C.glua_pop(L, 1) 142 | } 143 | 144 | methodName := args[0].(string) 145 | if len(args) > 1 { 146 | args = args[1:] 147 | } else { 148 | args = make([]interface{}, 0) 149 | } 150 | 151 | go func() { 152 | defer func() { 153 | if e := recover(); e != nil { 154 | err, ok := e.(error) 155 | if !ok { 156 | err = errors.New(fmt.Sprintf("%v", e)) 157 | } 158 | luaCtx.act.params = []interface{}{nil, err} 159 | } 160 | getScheduler().luaCtxQueue <- luaCtx 161 | }() 162 | method, ok := luaCtx.act.funcs[methodName] 163 | if ok { 164 | res, err := method(ctx, args...) 165 | luaCtx.act.params = []interface{}{res, err} 166 | } else { 167 | res, err := callExternMethod(ctx, methodName, args...) 168 | luaCtx.act.params = []interface{}{res, err} 169 | } 170 | }() 171 | } 172 | default: 173 | { 174 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "error"}) 175 | luaCtx.status = 3 176 | luaCtx.callback <- errors.New(C.GoString(C.glua_tostring(L, -1))) 177 | close(luaCtx.callback) 178 | v.destoryThread(threadId, L) 179 | } 180 | } 181 | } 182 | 183 | func (v *luaVm) resume(ctx context.Context, luaCtx *luaContext) { 184 | metricCounter("glua_vm_run_total", 1, map[string]string{ 185 | "vm_id": fmt.Sprintf("%d", v.stateId), 186 | }) 187 | metricGauge("glua_vm_memory_size", int64(C.glua_gc(v.state, C.LUA_GCCOUNT, 0)<<10+C.glua_gc(v.state, C.LUA_GCCOUNTB, 0)), map[string]string{ 188 | "vm_id": fmt.Sprintf("%d", v.stateId), 189 | }) 190 | 191 | defer func() { 192 | C.glua_gc(v.state, C.LUA_GCCOLLECT, 0) 193 | }() 194 | 195 | v.resumeCount-- 196 | L := v.threadDic[luaCtx.luaThreadId] 197 | pushToLua(L, luaCtx.act.params...) 198 | num := C.glua_gettop(L) 199 | ret := C.glua_resume(L, num) 200 | switch ret { 201 | case C.LUA_OK: 202 | { 203 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "success"}) 204 | luaCtx.status = 3 205 | count := int(C.glua_gettop(L)) 206 | res := make([]interface{}, count) 207 | for { 208 | count = int(C.glua_gettop(L)) 209 | if count == 0 { 210 | break 211 | } 212 | res[count-1] = pullFromLua(L, -1) 213 | C.glua_pop(L, 1) 214 | } 215 | switch len(res) { 216 | case 0: 217 | luaCtx.callback <- nil 218 | case 1: 219 | luaCtx.callback <- res[0] 220 | default: 221 | luaCtx.callback <- res 222 | } 223 | close(luaCtx.callback) 224 | v.destoryThread(luaCtx.luaThreadId, L) 225 | } 226 | case C.LUA_YIELD: 227 | { 228 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "yield"}) 229 | v.resumeCount++ 230 | luaCtx.status = 2 231 | 232 | count := int(C.glua_gettop(L)) 233 | args := make([]interface{}, count) 234 | for { 235 | count = int(C.glua_gettop(L)) 236 | if count == 0 { 237 | break 238 | } 239 | args[count-1] = pullFromLua(L, -1) 240 | C.glua_pop(L, 1) 241 | } 242 | 243 | methodName := args[0].(string) 244 | if len(args) > 1 { 245 | args = args[1:] 246 | } else { 247 | args = make([]interface{}, 0) 248 | } 249 | 250 | go func() { 251 | defer func() { 252 | if e := recover(); e != nil { 253 | err, ok := e.(error) 254 | if !ok { 255 | err = errors.New(fmt.Sprintf("%v", e)) 256 | } 257 | luaCtx.act.params = []interface{}{nil, err} 258 | } 259 | getScheduler().luaCtxQueue <- luaCtx 260 | }() 261 | 262 | method, ok := luaCtx.act.funcs[methodName] 263 | if ok { 264 | res, err := method(ctx, args...) 265 | luaCtx.act.params = []interface{}{res, err} 266 | } else { 267 | res, err := callExternMethod(ctx, methodName, args...) 268 | luaCtx.act.params = []interface{}{res, err} 269 | } 270 | }() 271 | } 272 | default: 273 | { 274 | metricCounter("glua_action_result_total", 1, map[string]string{"type": "error"}) 275 | luaCtx.status = 3 276 | luaCtx.callback <- errors.New(C.GoString(C.glua_tostring(L, -1))) 277 | close(luaCtx.callback) 278 | v.destoryThread(luaCtx.luaThreadId, L) 279 | } 280 | } 281 | } 282 | 283 | func (v *luaVm) destoryThread(threadId uintptr, L *C.struct_lua_State) { 284 | defer func() { 285 | C.glua_gc(v.state, C.LUA_GCCOLLECT, 0) 286 | }() 287 | 288 | cleanDummy(L) 289 | delete(v.threadDic, threadId) 290 | popThreadContext(threadId) 291 | var ( 292 | index C.int 293 | count C.int 294 | ) 295 | count = C.glua_gettop(v.state) 296 | for index = 1; index <= count; index++ { 297 | vType := C.glua_type(v.state, index) 298 | if vType == C.LUA_TTHREAD { 299 | ptr := C.glua_tothread(v.state, index) 300 | if ptr == L { 301 | C.glua_remove(v.state, index) 302 | L = nil 303 | return 304 | } 305 | } 306 | } 307 | } 308 | 309 | func (v *luaVm) destory() { 310 | C.glua_close(v.state) 311 | v.state = nil 312 | } 313 | -------------------------------------------------------------------------------- /wapper.go: -------------------------------------------------------------------------------- 1 | package glua 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | // #cgo CFLAGS: -I/usr/local/include/luajit-2.1 10 | // #cgo LDFLAGS: -L/usr/local/lib -lluajit -ldl -lm 11 | //#include "glua.h" 12 | import "C" 13 | 14 | func LuaNumberToInt64(value interface{}) (int64, error) { 15 | switch value.(type) { 16 | case C.lua_Number: 17 | { 18 | return int64(value.(C.lua_Number)), nil 19 | } 20 | default: 21 | { 22 | return 0, errors.New("Invalid Type") 23 | } 24 | } 25 | } 26 | 27 | func LuaNumberToInt32(value interface{}) (int32, error) { 28 | switch value.(type) { 29 | case C.lua_Number: 30 | { 31 | return int32(value.(C.lua_Number)), nil 32 | } 33 | default: 34 | { 35 | return 0, errors.New("Invalid Type") 36 | } 37 | } 38 | } 39 | 40 | func LuaNumberToInt(value interface{}) (int, error) { 41 | switch value.(type) { 42 | case C.lua_Number: 43 | { 44 | return int(value.(C.lua_Number)), nil 45 | } 46 | default: 47 | { 48 | return 0, errors.New("Invalid Type") 49 | } 50 | } 51 | } 52 | 53 | func LuaNumberToFloat32(value interface{}) (float32, error) { 54 | switch value.(type) { 55 | case C.lua_Number: 56 | { 57 | return float32(value.(C.lua_Number)), nil 58 | } 59 | default: 60 | { 61 | return 0.0, errors.New("Invalid Type") 62 | } 63 | } 64 | } 65 | 66 | func LuaNumberToFloat64(value interface{}) (float64, error) { 67 | switch value.(type) { 68 | case C.lua_Number: 69 | { 70 | return float64(value.(C.lua_Number)), nil 71 | } 72 | default: 73 | { 74 | return 0.0, errors.New("Invalid Type") 75 | } 76 | } 77 | } 78 | 79 | func pushToLua(L *C.struct_lua_State, args ...interface{}) { 80 | for _, arg := range args { 81 | switch arg.(type) { 82 | case string: 83 | { 84 | C.glua_pushlstring(L, C.CString(arg.(string)), C.size_t(len([]byte(arg.(string))))) 85 | } 86 | case float64: 87 | C.glua_pushnumber(L, C.lua_Number(arg.(float64))) 88 | case float32: 89 | C.glua_pushnumber(L, C.lua_Number(arg.(float32))) 90 | case uint64: 91 | C.glua_pushnumber(L, C.lua_Number(arg.(uint64))) 92 | case int64: 93 | C.glua_pushnumber(L, C.lua_Number(arg.(int64))) 94 | case uint32: 95 | C.glua_pushnumber(L, C.lua_Number(arg.(uint32))) 96 | case int32: 97 | C.glua_pushnumber(L, C.lua_Number(arg.(int32))) 98 | case uint16: 99 | C.glua_pushnumber(L, C.lua_Number(arg.(uint16))) 100 | case int16: 101 | C.glua_pushnumber(L, C.lua_Number(arg.(int16))) 102 | case uint8: 103 | C.glua_pushnumber(L, C.lua_Number(arg.(uint8))) 104 | case int8: 105 | C.glua_pushnumber(L, C.lua_Number(arg.(int8))) 106 | case uint: 107 | C.glua_pushnumber(L, C.lua_Number(arg.(uint))) 108 | case int: 109 | C.glua_pushnumber(L, C.lua_Number(arg.(int))) 110 | case bool: 111 | if arg.(bool) { 112 | C.glua_pushboolean(L, C.int(1)) 113 | } else { 114 | C.glua_pushboolean(L, C.int(0)) 115 | } 116 | case error: 117 | { 118 | str := arg.(error).Error() 119 | C.glua_pushlstring(L, C.CString(str), C.size_t(len([]byte(str)))) 120 | } 121 | case []byte: 122 | { 123 | C.glua_pushlstring(L, C.CString(string(arg.([]byte))), C.size_t(len(arg.([]byte)))) 124 | } 125 | case map[string]interface{}: 126 | { 127 | pushMapToLua(L, arg.(map[string]interface{})) 128 | } 129 | case []interface{}: 130 | { 131 | pushArrayToLua(L, arg.([]interface{})) 132 | } 133 | case nil: 134 | { 135 | C.glua_pushnil(L) 136 | } 137 | default: 138 | { 139 | ptr := pushDummy(L, arg) 140 | C.glua_pushlightuserdata(L, ptr) 141 | } 142 | } 143 | } 144 | } 145 | 146 | func pushArrayToLua(L *C.struct_lua_State, data []interface{}) { 147 | C.glua_createtable(L, 0, 0) 148 | if len(data) == 0 { 149 | return 150 | } 151 | for index, value := range data { 152 | C.glua_pushnumber(L, C.lua_Number(index+1)) 153 | pushToLua(L, value) 154 | C.glua_settable(L, -3) 155 | } 156 | } 157 | 158 | func pushMapToLua(L *C.struct_lua_State, data map[string]interface{}) { 159 | C.glua_createtable(L, 0, 0) 160 | if len(data) == 0 { 161 | return 162 | } 163 | for key, value := range data { 164 | C.glua_pushlstring(L, C.CString(key), C.size_t(len([]byte(key)))) 165 | pushToLua(L, value) 166 | C.glua_settable(L, -3) 167 | } 168 | } 169 | 170 | func pullLuaTable(_L *C.struct_lua_State) interface{} { 171 | keys := make([]interface{}, 0) 172 | values := make([]interface{}, 0) 173 | 174 | numKeyCount := 0 175 | var ( 176 | key interface{} 177 | value interface{} 178 | ) 179 | C.glua_pushnil(_L) 180 | for C.glua_next(_L, -2) != 0 { 181 | kType := C.glua_type(_L, -2) 182 | if kType == 4 { 183 | key = C.GoString(C.glua_tostring(_L, -2)) 184 | } else { 185 | key, _ = LuaNumberToInt(C.glua_tonumber(_L, -2)) 186 | numKeyCount = numKeyCount + 1 187 | } 188 | vType := C.glua_type(_L, -1) 189 | switch vType { 190 | case 0: 191 | { 192 | C.glua_pop(_L, 1) 193 | continue 194 | } 195 | case 1: 196 | { 197 | temp := C.glua_toboolean(_L, -1) 198 | if temp == 1 { 199 | value = true 200 | } else { 201 | value = false 202 | } 203 | } 204 | case 2: 205 | { 206 | ptr := C.glua_touserdata(_L, -1) 207 | target, err := findDummy(_L, ptr) 208 | if err != nil { 209 | C.glua_pop(_L, 1) 210 | continue 211 | } 212 | value = target 213 | } 214 | case 3: 215 | { 216 | temp := float64(C.glua_tonumber(_L, -1)) 217 | if math.Ceil(temp) > temp { 218 | value = temp 219 | } else { 220 | value = int64(temp) 221 | } 222 | } 223 | case 4: 224 | { 225 | value = C.GoString(C.glua_tostring(_L, -1)) 226 | } 227 | case 5: 228 | { 229 | value = pullLuaTable(_L) 230 | } 231 | } 232 | keys = append(keys, key) 233 | values = append(values, value) 234 | C.glua_pop(_L, 1) 235 | } 236 | if numKeyCount == len(keys) { 237 | return values 238 | } 239 | if numKeyCount == 0 { 240 | result := make(map[string]interface{}) 241 | for index, key := range keys { 242 | result[key.(string)] = values[index] 243 | } 244 | return result 245 | } else { 246 | result := make(map[interface{}]interface{}) 247 | for index, key := range keys { 248 | result[key] = values[index] 249 | } 250 | return result 251 | } 252 | } 253 | 254 | func pullFromLua(L *C.struct_lua_State, index int) interface{} { 255 | vType := C.glua_type(L, C.int(index)) 256 | switch vType { 257 | case C.LUA_TBOOLEAN: 258 | { 259 | res := C.lua_toboolean(L, C.int(index)) 260 | if res == 0 { 261 | return false 262 | } 263 | return true 264 | } 265 | case C.LUA_TNUMBER: 266 | { 267 | temp := float64(C.glua_tonumber(L, -1)) 268 | if math.Ceil(temp) > temp { 269 | return temp 270 | } else { 271 | return int64(temp) 272 | } 273 | } 274 | case C.LUA_TSTRING: 275 | { 276 | return C.GoString(C.glua_tostring(L, C.int(index))) 277 | } 278 | case C.LUA_TTABLE: 279 | { 280 | return pullLuaTable(L) 281 | } 282 | case C.LUA_TLIGHTUSERDATA: 283 | { 284 | ptr := C.glua_touserdata(L, C.int(index)) 285 | target, err := findDummy(L, ptr) 286 | if err != nil { 287 | return nil 288 | } else { 289 | return target 290 | } 291 | } 292 | case C.LUA_TNIL: 293 | { 294 | return nil 295 | } 296 | default: 297 | { 298 | panic(errors.New(fmt.Sprintf("Unsupport Type %d", vType))) 299 | } 300 | } 301 | return nil 302 | } 303 | --------------------------------------------------------------------------------