#golang #lua #压测
https://github.com/yuin/gopher-lua#usage
# 简单使用
- 首先声明一个 lua 虚拟机: L := lua.NewState () 返回一个 LState Struct
 - 然后可以执行 lua 格式的字符串或者 File
- lua.DoString( 
print("hello")) - lua.DoFile (lua 脚本的路径)
 
 - lua.DoString( 
 
LState 定义如下:
type LState struct {
	G       *Global
	Parent  *LState
	Env     *LTable
	Panic   func(*LState)
	Dead    bool
	Options Options
	stop         int32
	reg          *registry
	stack        callFrameStack
	alloc        *allocator
	currentFrame *callFrame
	wrapped      bool
	uvcache      *Upvalue
	hasErrorFunc bool
	mainLoop     func(*LState, *callFrame)
	ctx          context.Context
}
- Get 方法 获取栈中的变量
 
# 数据模型
gopher-lua 中的说有变量值都是一个 LValue, 是 go 语言中的 interface,包含两个方法:
- String()string
 - Type() LValueType
 
type LValue interface {  | |
String() string  | |
Type() LValueType  | |
assertFloat64() (float64, bool)  | |
assertString() (string, bool)  | |
assertFunction() (*LFunction, bool)  | |
} | 
该接口的实现包括如下类:
| Type name | Go type | Type() value | Constants | 
|---|---|---|---|
LNilType | 
(constants) | LTNil | 
LNil | 
LBool | 
(constants) | LTBool | 
LTrue ,  LFalse | 
LNumber | 
float64 | LTNumber | 
- | 
LString | 
string | LTString | 
- | 
LFunction | 
struct pointer | LTFunction | 
- | 
LUserData | 
struct pointer | LTUserData | 
- | 
LState | 
struct pointer | LTThread | 
- | 
LTable | 
struct pointer | LTTable | 
- | 
LChannel | 
chan LValue | LTChannel | 
- | 
- lv.Type () 可以获取类型
 - 原表不可用;没有错误捕捉
 
# Callstack & Registry size
LState 的调用栈的大小控制着脚本中 Lua 函数的最大调用深度(Go 函数的调用不算在内)。
LState 的注册表实现了对调用函数(包括 Lua 和 Go 函数)和表达式中的临时变量的栈存储。它的存储需求将随着调用堆栈的使用和代码的复杂性而增加。
注册表和调用堆栈都可以被设置为固定大小或自动大小。
L := lua.NewState(lua.Options{  | |
RegistrySize: 1024 * 20, // this is the initial size of the registry  | |
RegistryMaxSize: 1024 * 80, // this is the maximum size that the registry can grow to. If set to `0` (the default) then the registry will not auto grow  | |
RegistryGrowStep: 32, // this is how much to step up the registry by each time it runs out of space. The default is `32`.  | |
})  | |
defer L.Close()  | 
# API
# 从 lua 中调用 go 函数
func Double(L *lua.LState) int {  | |
lv := L.ToInt(1) /* get argument */  | |
L.Push(lua.LNumber(lv * 2)) /* push result */  | |
return 1 /* number of results */  | |
} | |
func main() {  | |
L := lua.NewState()  | |
defer L.Close()  | |
L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */  | |
if err := L.DoString(`print(double(20))`); err != nil {  | |
panic(err)  | |
	} | |
} | 
注册为 lua 函数之后,会变成一个 LGFunction 类型;
支持协程中运行;
# 加载 lua 内置库的函数
func main() {  | |
L := lua.NewState(lua.Options{SkipOpenLibs: true})  | |
defer L.Close()  | |
for _, pair := range []struct {  | |
        n string | |
        f lua.LGFunction | |
}{  | |
{lua.LoadLibName, lua.OpenPackage}, // Must be first  | |
{lua.BaseLibName, lua.OpenBase},  | |
{lua.TabLibName, lua.OpenTable},  | |
} {  | |
if err := L.CallByParam(lua.P{  | |
Fn: L.NewFunction(pair.f),  | |
NRet: 0,  | |
Protect: true,  | |
}, lua.LString(pair.n)); err != nil {  | |
panic(err)  | |
        } | |
    } | |
if err := L.DoFile("main.lua"); err != nil {  | |
panic(err)  | |
    } | |
} | 
# 在 go 中创建一个 lua 的模块
- 
首先定义一组方法 类型为 map [string] lua.LGFuntion
 - 
然后调用 SetFuncs 将函数表分配给一个 lua table,作为一个模块,获取到一个 LTable
 - 
然后将模块 push 到栈
func Loader(L *lua.LState) int {
// register functions to the tablemod := L.SetFuncs(L.NewTable(), exports)
// register other stuffL.SetField(mod, "name", lua.LString("value"))
// returns the moduleL.Push(mod)
return 1
} - 
通过 PreLoadModule(name, 注册方法)将模块注册到虚拟机中
L.PreloadModule("mymodule", mymodule.Loader)
 
# 在 go 中调用 lua 方法
if err := L.CallByParam(lua.P{  | |
Fn: L.GetGlobal("double"), //lua 方法名  | |
NRet: 1, //  | |
Protect: true,  | |
}, lua.LNumber(10)); err != nil {  | |
panic(err)  | |
} | 
- CallByParam 方法 第一个参数 lua.P 结构; 第二个参数 参数
 - 通过 lua.P 结构进行调用
 - 实际使用中 函数参数也可以使用提前设置全局变量的方式来实现
 
# 自定义类型
支持在 Go 中自定义新类型
type Person struct {  | |
    Name string | |
} | |
const luaPersonTypeName = "person"  | |
// 注册类型 | |
func registerPersonType(L *lua.LState) {  | |
mt := L.NewTypeMetatable(luaPersonTypeName) // 新建一个元表  | |
L.SetGlobal("person", mt) // 元表设置为全局变量  | |
    // static attributes | |
L.SetField(mt, "new", L.NewFunction(newPerson)) // 注册方法到元表中 静态放啊  | |
    // methods | |
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods)) // 注册方法到元表  | |
} | |
// Constructor | |
func newPerson(L *lua.LState) int { //go 方法  | |
person := &Person{L.CheckString(1)}  | |
ud := L.NewUserData()  | |
ud.Value = person  | |
L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))  | |
L.Push(ud)  | |
return 1  | |
} | |
// Checks whether the first lua argument is a *LUserData with *Person and returns this *Person. | |
func checkPerson(L *lua.LState) *Person { // 检查类型  | |
ud := L.CheckUserData(1)  | |
if v, ok := ud.Value.(*Person); ok {  | |
        return v | |
    } | |
L.ArgError(1, "person expected")  | |
return nil  | |
} | |
var personMethods = map[string]lua.LGFunction{ // 方法表  | |
"name": personGetSetName,  | |
} | |
// Getter and setter for the Person#Name | |
func personGetSetName(L *lua.LState) int { // 属性的 Getter 和 Setter 在 lua 中通过 p:name () 调用  | |
p := checkPerson(L)  | |
if L.GetTop() == 2 {  | |
p.Name = L.CheckString(2)  | |
return 0  | |
    } | |
L.Push(lua.LString(p.Name))  | |
return 1  | |
} | |
func main() {  | |
L := lua.NewState()  | |
defer L.Close()  | |
registerPersonType(L)  | |
if err := L.DoString(`  | |
        p = person.new("Steeve")	 | |
        print(p:name("新名字")) --   | |
print(p:name())  | |
        p:name("Alice") | |
print(p:name()) -- "Alice"  | |
`); err != nil {  | |
panic(err)  | |
    } | |
} | 
# 共享 lua 字节代码
调用 DoFile 将加载一个 Lua 脚本,将其编译为字节码,并在一个 LState 中运行字节码。
如果你有多个 LState,它们都需要运行同一个脚本,你可以在它们之间共享字节码,这将节省内存。共享字节码是安全的,因为它是只读的,不能被 lua 脚本所改变。
// CompileLua reads the passed lua file from disk and compiles it. | |
func CompileLua(filePath string) (*lua.FunctionProto, error) {  | |
file, err := os.Open(filePath)  | |
defer file.Close()  | |
if err != nil {  | |
return nil, err  | |
    } | |
reader := bufio.NewReader(file)  | |
chunk, err := parse.Parse(reader, filePath)  | |
if err != nil {  | |
return nil, err  | |
    } | |
proto, err := lua.Compile(chunk, filePath)  | |
if err != nil {  | |
return nil, err  | |
    } | |
return proto, nil  | |
} | |
// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent | |
// to calling DoFile on the LState with the original source file. | |
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {  | |
lfunc := L.NewFunctionFromProto(proto)  | |
L.Push(lfunc)  | |
return L.PCall(0, lua.MultRet, nil)  | |
} | 
# go 协程
LState 不是 goroutine-safe。建议每个 goroutine 使用一个 LState,并通过使用通道在 goroutine 之间通信。
通道在 GopherLua 中由通道对象表示。而一个通道表提供了执行通道操作的函数。
有些对象不能通过通道发送,因为它本身有非 goroutine 安全的对象。
一个线程 (state)
 一个函数
一个用户数据
一个有元数据的表