Lua 런타임 #

게임 서버에서는 Lua에서 작성된 사용자 지정 로직을 로드하고 실행할 수 있습니다. 이것은 클라이언트에서 실행을 원하지 않는 게임 코드를 실행하거나 클라이언트가 선택하지 않은 입력을 제공할 때 유용합니다.

이 Nakama 기능은 다른 시스템에서 람다 또는 클라우드 함수와 유사하다고 생각할 수 있습니다. 사용자가 게임을 플레이할 때마다 보상을 부여하려고 할 때 유용하게 사용할 수 있습니다.

Go 또는 TypeScript에서 서버 로직을 작성하는 것과 달리, Lua에서 코드를 작성할 때 도구 체인이나 다른 설정이 필요하지 않습니다. Lua는 강력한 내장형 스크립트 언어이며 컴파일이나 트랜스파일이 필요하지 않습니다. 빠르고 쉽게 실행하기를 원하는 경우 유용하게 사용할 수 있습니다.

Lua 코드에서 작성하는 방법에 대해서는 공식 문서를 참조하십시오.

개발 코드 #

전체 Lua Nakama 모듈 함수 참조는 여기를 참조하십시오.

시작하기 전에, 프로젝트에 대해서 새로운 폴더를 생성하고 선택한 편집기(예: VS Code)에서 열어줍니다.

modules(이)라고 하는 새로운 폴더를 만들어 시작하고 내부에 main.lua(이)라고 하는 새로운 파일을 만듭니다. 아래의 코드는 "Logger"을(를) 사용하여 메시지를 작성하는 Hello World에 대한 간단한 예시입니다.

1
2
local nk = require("nakama")
nk.logger_info("Hello World!")

제한 사항 #

호환성 #

Lua 런타임은 새로운 버전에서 백포트된 작은 일련의 추가 패키지를 포함하여 Lua 5.1과 호환되어 실행됩니다. 사용 가능한 함수를 참조하십시오. 최상의 결과를 얻기 위해서는 Lua 모듈과 제3자 라이브러리가 Lua 5.1과 호환되어야 합니다.

Lua 런타임 코드는 Lua C API 또는 확장자를 사용할 수 없습니다. 코드와 제3자 라이브러리가 순수 Lua 5.1인지 확인합니다.

서버에 내장된 Lua 가상 머신은 제한된 Lua 표준 라이브러리 모듈 세트를 사용합니다. 코드 샌드박스가 운영 시스템 입력/출력 또는 파일 시스템을 변조시키지 않도록 합니다.

사용 가능한 Lua 모듈의 목록은 다음과 같습니다:

  • 기본 모듈
  • math
  • string
  • table
  • bit32
  • os의 하위 집합(clock, difftime, date, time 함수만)

글로벌 상태 #

Lua 런타임 코드는 인스턴스된 컨텍스트(VM 풀)에서 실행됩니다. 글로벌 변수를 사용하여 메모리에 상태를 저장하거나 다른 Lua 처리기 또는 함수 호출과 소통할 수 없습니다.

단일 스레드 #

다중 스레드 처리(코루틴)는 Lua 런타임의 Nakama 실행에서 지원되지 않습니다.

샌드박싱 #

Lua 런타임 코드는 전체 샌드박스로 적용되어 파일 시스템, 입력/출력 장치에 액세스할 수 없고, OS 스레드나 프로세스를 생성할 수 없습니다.

이렇게 하면 서버 내에서 Lua 모듈로 인해 심각한 오류가 발생하지 않습니다. 런타임 코드는 예상치 못한 클라이언트 연결 해제를 발생시키거나 메인 서버 프로세스에 영향을 미치지 않습니다.

오류 처리 #

Lua 오류 처리는 오류 반환 값 대신에 발생한 오류를 사용합니다. 함수 실행 시 발생하는 오류를 트랩하려면 pcall을(를) 통해서 “보호된 호출"로 실행해야 합니다.

1
2
3
4
5
6
7
8
9
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 코드에서 이 패턴을 사용하는 것이 좋습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
서버 로거 수준이 info(기본 수준) 이하로 설정된 경우, 서버는 Lua 스택 추적을 클라이언트로 반환합니다. 이것은 디버깅에 유용하지만, 프로덕션에서는 비활성화되어야 합니다.

클라이언트로 오류 반환 #

자체적으로 사용자 지정 런타임 코드를 작성하는 경우, 요청을 처리하는 과정에서 발생하는 오류가 클라이언트로 적절하게 전달되는지 확인해야 합니다. 클라이언트로 반환되는 오류에 명확한 정보를 전달하는 오류 메시지와 정확한 HTTP 상태 코드가 포함되어야 한다는 의미입니다.

내부적으로 Nakama 런타임은 클라이언트로 오류를 반환할 때 gRPC 오류 코드를 사용하여 정확한 HTTP 상태 코드로 전환합니다.

아래와 같이 gRPC 오류 코드를 Lua 모듈에서 상수로 정의할 수 있습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 호출과 사전 후크에서 정확한 오류를 반환하는 방법에 대한 예시입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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를 사용할 수 있습니다:

이 작업을 완료하고 나면 게임 서버를 실행하여 코드를 로드할 수 있습니다:

1
nakama --logger.level DEBUG

서버 로그는 위에서 작성한 코드가 시작 시 로드되고 실행되었는지 나타내는 출력이나 유사한 항목으로 표시됩니다.

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

다음 단계 #

다음의 Nakama 기능을 포함하는 Nakama 프로젝트 템플릿을 살펴보십시오:

Related Pages