lua-protobuf-rs 0.3.0

lua protobuf
Documentation

lua-protobuf-rs

Available languages: English | 中文

This project provides a Lua library for parsing Protobuf, built on top of Rust’s protobuf implementation.

It allows you to:

  • Load .proto files at runtime
  • Parse Protobuf binary data into Lua tables
  • Encode Lua tables into Protobuf binary data
  • Use Protobuf reflection features in Lua

The bound APIs can be found here, and almost all APIs are exposed.


Usage

Parse proto directly in Lua

--- @type LuaProtoc
local luaProtoc = require("lua_protobuf_rs")

---@language "protobuf"
local proto = [[
syntax="proto3";

message Player{
  int64 id = 1;
  int64 world_id = 2;
  string nickname = 3;
  int32 exp = 4;
}

message LoginRequest{
  int64 id = 1;
  int64 world_id = 2;
}

message LoginResponse{
  Player player = 1;
}
]]

local protoc = luaProtoc.parse_proto(proto)

local player = {
    id = 2347239423213,
    world_id = 234872389,
    nickname = "mikai233",
    exp = 22000,
}

local player_bytes = protoc:encode("Player", player)
local decode_player = protoc:decode("Player", player_bytes)
print(decode_player.id)

local login_response_bytes = protoc:encode("LoginResponse", {})
local decode_login_response = protoc:decode("LoginResponse", login_response_bytes)
print(decode_login_response.player.id)

Parse proto files in Lua

  • player.proto
syntax = "proto3";

package com.mikai233;

message Player{
  int64 id = 1;
  int64 world_id = 2;
  string nickname = 3;
  int32 exp = 4;
}
  • login.proto
syntax = "proto3";
import "player.proto";

package com.mikai233;

message LoginRequest{
  int64 id = 1;
  int64 world_id = 2;
}

message LoginResponse{
  Player player = 1;
}
--- @type LuaProtoc
local luaProtoc = require("lua_protobuf_rs")

local protos = luaProtoc.list_protos({ "proto" })

local protoc = luaProtoc.parse_files(protos, { "proto" })

local player = {
    id = 2347239423213,
    world_id = 234872389,
    nickname = "mikai233",
    exp = 22000,
}

local player_bytes = protoc:encode("com.mikai233.Player", player)
local decode_player = protoc:decode("com.mikai233.Player", player_bytes)
print(decode_player.id)

local login_response_bytes = protoc:encode("com.mikai233.LoginResponse", {})
local decode_login_response = protoc:decode("com.mikai233.LoginResponse", login_response_bytes)
print(decode_login_response.player.id)

Reflection

For more reflection APIs, please refer to the documentation.

--- @type LuaProtoc
local luaProtoc = require("lua_protobuf_rs")

local protos = luaProtoc.list_protos({ "proto" })

local protoc = luaProtoc.parse_files(protos, { "proto" })

local player_descriptor = protoc:message_descriptor_by_name("com.mikai233.Player")

for _, field in pairs(player_descriptor:fields()) do
    print("field name: " .. field:name() .. " number: " .. field:number())
end

print("====")

local login_response_descriptor = protoc:message_descriptor_by_name("com.mikai233.LoginResponse")

for _, field in pairs(login_response_descriptor:fields()) do
    local rt = field:runtime_field_type()
    if rt.singular then
        local singular = rt.singular
        print(singular.message:name())
    end
end

Proto code hints

You can use gen_lua to generate Lua proto template files for better development experience. This project uses annotations based on the EmmyLua plugin.

---@class LoginRequest
---@field id number
---@field world_id number
local LoginRequest

---@class LoginResponse
---@field player Player
local LoginResponse

xLua integration

Set the environment variables LUA_LIB_NAME and LUA_LIB to point to the xLua header directory and library name, then recompile this project. ⚠️ Make sure the xLua version matches the Lua version used in this project.

[DllImport("lua_protobuf_rs", CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_lua_protobuf_rs(System.IntPtr L);

[MonoPInvokeCallback(typeof(LuaDLL.lua_CSFunction))]
public static int LoadProtobufRs(System.IntPtr L)
{
    return luaopen_lua_protobuf_rs(L);
}

Build

Thanks to Cargo, building is very simple. Just install Rust, then run:

cargo build --release

to get the library file for the current platform.

For different Lua versions, modify the default field in Cargo.toml and rebuild.

Cross-compilation

If you need to build for other platforms, you can use cross-rs , which requires Docker but is very straightforward.

  • Build for Linux: cross build --target x86_64-unknown-linux-gnu --release

  • Build for Android: cross build --target armv7-linux-androideabi --release

Notes

For non-oneof fields, when parsing binary messages into a Lua table, unset fields will get default values. For oneof fields, if no value is set, then all fields will be absent. That means: in a oneof, only one field can exist at a time, or none at all.