#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 table
mod := L.SetFuncs(L.NewTable(), exports)
// register other stuff
L.SetField(mod, "name", lua.LString("value"))
// returns the module
L.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)
一个函数
一个用户数据
一个有元数据的表