# Lua 运行时

**URL:** https://heroiclabs.com/docs/zh/nakama/server-framework/lua-runtime/
**Summary:** 学习如何用 Lua 编写游戏服务器自定义逻辑，从而配置和开发项目。

---


# Lua 运行库

游戏服务器可让您加载和运行用 Lua 编写的自定义逻辑。这适用于实现您不想在客户端上运行的游戏代码，或者信任客户端提供未经检查的输入。 

您可以将此 Nakama 功能想象为类似于其他系统中的 Lambda 或云功能。您想授予用户[玩游戏日奖](../../guides/concepts/daily-rewards/)就是一个很好的用例。

与用 [Go](../go-runtime/) 或 [TypeScript](../typescript-runtime/) 编写服务器逻辑不同，在用 Lua 编写代码时，不需要工具链或其他设置。Lua 是一种功能强大的可嵌入脚本语言，不需要编译或传输。如果您想快速轻松地设置和运行，这是一个不错的选择。

您可以在[官方文档](https://www.lua.org/docs.html)中学习如何编写 Lua 代码。

## 开发代码

点击查看完整的 Lua [Nakama 模块函数参考](./function-reference/)。

开始前，用一个您选择的编辑器（例如 VS Code）新建一个项目文件夹并打开它。

首选创建一个名为 `modules` 的新文件夹，然后在其中创建一个名为 `main.lua` 的新文件。以下代码为一个简单的 Hello World 示例，使用了 `"Logger"` 来编写消息。

```lua
local nk = require("nakama")
nk.logger_info("Hello World!")
```

## 限制

### 兼容性

Lua 运行库是一个与 Lua 5.1 兼容的实现，它有一小套从较新版本向后移植的附加包 — 请参阅[可用函数](./function-reference/)。为了获得最佳结果，请确保您的 Lua 模块和任何第三方库与 Lua 5.1 兼容。

{{< note important >}}
Lua 运行时代码不能使用 Lua C API 或扩展。确保您的代码和任何第三方库为纯 Lua 5.1。
{{< / note >}}

服务器中嵌入的 Lua 虚拟机使用一组受限的 Lua 标准库模块。这可以确保代码沙盒不会篡改操作系统输入/输出或文件系统。

可用的 Lua 模块有： 
* 基本模块
* `math`
* `string`
* `table`
* `bit32`
*  `os` 子集（仅有 `clock`、`difftime`、`date` 和 `time` 函数）

### 全局状态

Lua 运行时代码在实例上下文（VM 池）中执行。不能用全局变量在内存中存储状态或与其他 Lua 进程或函数调用通信。

### 单一线程

Lua 运行库的 Nakama 实现不支持使用多线程处理（协同程序）。 

### 沙盒

Lua 运行时代码是完全沙盒化的，无法访问文件系统、输入/输出设备或生成操作系统线程或进程。 

这样服务器可保证 Lua 模块不会导致致命错误 — 运行时代码不会触发意外的客户端断开连接或影响主服务器进程。

## 错误处理

Lua 错误处理使用引发的错误，而不是错误返回值。如果您想捕获在执行函数时发生的错误，则需要通过 `pcall` 将其作为“受保护调用”来执行。

```lua
local function will_error()
  error("This function will always throw an error!")
end

if pcall(will_error) then
  -- No errors with "will_error".
else
  -- Handle errors.
end
```

函数 `will_error` 使用 Lua 中的 `error` 函数抛出带有解释消息的错误。`pcall` 将调用 `will_error` 函数，并捕获任何错误。然后我们可以根据需要处理成功或错误案例。我们建议将这种模式与 Lua 代码一起使用。

```lua
local nk = require("nakama")

local status, result = pcall(nk.users_get_username, {"22e9ed62"})
if (not status) then
  nk.logger_error(string.format("Error occurred: %q", result))
else
  for _, u in ipairs(result)
  do
    local message = string.format("id: %q, display name: %q", u.id, u.display_name)
    nk.logger_info(message) -- Will appear in logging output.
  end
end
```

{{< note type="important" title="Important" >}}
如果将服务器记录器设置为 `info`（默认级别）或更低，服务器将向客户端返回 Lua 堆栈跟踪。这对于调试很实用，但应在生产中禁用。
{{< / note >}}

## 向客户端返回错误

在编写自己的自定义运行时代码时，应确保在处理请求时发生的任何错误都将会以适当方式传回客户端。这意味着返回给客户端的错误应该包含一条明确的告知错误消息和一个适当的 HTTP 状态代码。

Nakama 运行库的内部使用 gRPC 错误代码，并在将错误返回给客户端时将其转换为适当的 HTTP 状态代码。

您可以在 Lua 模块中将 gRPC 错误代码定义为常量，如下所示：

``` lua
local error_codes = {
  OK                  = 0,  -- HTTP 200
  CANCELED            = 1,  -- HTTP 499
  UNKNOWN             = 2,  -- HTTP 500
  INVALID_ARGUMENT    = 3,  -- HTTP 400
  DEADLINE_EXCEEDED   = 4,  -- HTTP 504
  NOT_FOUND           = 5,  -- HTTP 404
  ALREADY_EXISTS      = 6,  -- HTTP 409
  PERMISSION_DENIED   = 7,  -- HTTP 403
  RESOURCE_EXHAUSTED  = 8,  -- HTTP 429
  FAILED_PRECONDITION = 9,  -- HTTP 400
  ABORTED             = 10, -- HTTP 409
  OUT_OF_RANGE        = 11, -- HTTP 400
  UNIMPLEMENTED       = 12, -- HTTP 501
  INTERNAL            = 13, -- HTTP 500
  UNAVAILABLE         = 14, -- HTTP 503
  DATA_LOSS           = 15, -- HTTP 500
  UNAUTHENTICATED     = 16  -- HTTP 401
}
```

以下示例显示如何同时在 [RPC](../introduction/#rpc-functions) 调用和 [Before 挂钩](../introduction/hooks/#before-hooks)中返回适当的错误。

``` lua
nk.register_rpc(function(context, payload)
  -- ... check if a guild already exists and set value of `already_exists` accordingly
  local already_exists = true

  if already_exists then
    error({ "guild name is in use", error_codes.ALREADY_EXISTS })
  end

  return nk.json_encode({ success = true })
end, "lua_create_guild")

nk.register_req_before(function(context, payload)
	-- Only match custom Id in the format "cid-000000"
  if not string.match(payload.account.id, "^cid%-%d%d%d%d%d%d$") then
    error({ "input contained invalid data", error_codes.INVALID_ARGUMENT})
  end

  return payload
end, "AuthenticateCustom")
```


### 全局状态

Lua 运行库可以使用全局变量在内存中存储状态以及根据需要存储和共享数据，但并发和访问控制需要由开发人员来处理。

不建议共享状态，应在运行时代码中避免，因为在多节点环境中**不受支持**。

## 运行项目

您可以将 Docker 与 [Compose 文件](../../getting-started/install/docker/)一起用于本地开发，或为以下系统设置二进制环境：

- [Linux](../../getting-started/install/linux/)
- [Windows](../../getting-started/install/windows/)
- [macOS](../../getting-started/install/macos/)

完成此步骤后，您可以运行游戏服务器，使其加载代码：

``` shell
nakama --logger.level DEBUG
```

服务器日志将显示此输出或类似输出，显示我们在启动时加载并执行了上面编写的代码。

``` json
{"level":"info","ts":"...","caller":"server/runtime_lua_nakama.go:1742","msg":"Hello World!","runtime":"lua"}
```

## 后续步骤

请查看 [Nakama 项目模板](https://github.com/heroiclabs/nakama-project-template)，其涉及以下 Nakama 功能：

- [权威多人游戏比赛处理程序](../../concepts/multiplayer/authoritative/)
- [应用程序内通知](../../concepts/notifications/)
- [存储](../../concepts/storage/collections/)
- [RPC](../introduction/#functionality)
- [用户钱包](../../concepts/user-accounts/#virtual-wallet)
