# lua-protobuf-rs
This project provides a Lua library for parsing Protobuf, built on top of
Rust’s [protobuf](https://github.com/stepancheg/rust-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](https://docs.rs/protobuf/latest/protobuf/), and almost all APIs are exposed.
---
# Usage
## Parse proto directly in Lua
```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
```protobuf
syntax = "proto3";
package com.mikai233;
message Player{
int64 id = 1;
int64 world_id = 2;
string nickname = 3;
int32 exp = 4;
}
```
- login.proto
```protobuf
syntax = "proto3";
import "player.proto";
package com.mikai233;
message LoginRequest{
int64 id = 1;
int64 world_id = 2;
}
message LoginResponse{
Player player = 1;
}
```
```lua
--- @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.
```lua
--- @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](https://github.com/EmmyLua/IntelliJ-EmmyLua)
plugin.
```lua
---@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.
```csharp
[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:
```shell
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](https://github.com/cross-rs/cross) , 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.