Networking
Status: Partly implemented. Custom payload channels both ways (inside vanilla
minecraft:custom_payloadpackets) with byte buffers. Sending to groups (tracking players, a chunk, a level), checking whether the peer has a channel, typed codecs, configuration-phase payloads, a mod-list handshake and packet listeners are Planned.
Function table: net. Guide: Custom packets (payloads).
Payload channels
typedef struct cl_payload_desc {
uint32_t struct_size;
cl_str id; // "ruby_tools:mana_sync", the channel
cl_payload_dir dir; // CL_PAYLOAD_TO_CLIENT, CL_PAYLOAD_TO_SERVER, CL_PAYLOAD_BOTH_WAYS (default)
uint32_t max_size; // bytes, default 32768
cl_payload_fn handler; // (cl_entity sender, cl_buf data, void* user): sender is the player on the server, null on the client
void* user;
} cl_payload_desc;
/* CL_PAYLOAD_DESC_INIT */
cl_result cl_payload_register(const cl_payload_desc* desc, cl_holder* out); // MAIN (lifecycle.register)
cl_result cl_net_send_to_server(cl_str id, cl_buf data); // client, TICK
cl_result cl_net_send_to_player(cl_entity player, cl_str id, cl_buf data); // server, TICK
Register the channel on both sides (a mod with "side": "both" runs the same registration on the client and the server). The handler runs on the receiving side’s tick; data is borrowed for the call. Payloads travel inside vanilla custom_payload packets, so proxies pass them through and vanilla peers ignore them.
Buffers (owned)
cl_buf cl_buf_create(size_t reserve);
void cl_buf_release(cl_buf b);
size_t cl_buf_size(cl_buf b);
size_t cl_buf_remaining(cl_buf b); // unread bytes
cl_result cl_buf_write_varint(cl_buf b, int32_t v); cl_result cl_buf_read_varint(cl_buf b, int32_t* out);
cl_result cl_buf_write_i32(cl_buf b, int32_t v); cl_result cl_buf_read_i32(cl_buf b, int32_t* out);
cl_result cl_buf_write_i64(cl_buf b, int64_t v); cl_result cl_buf_read_i64(cl_buf b, int64_t* out);
cl_result cl_buf_write_f32(cl_buf b, float v); cl_result cl_buf_read_f32(cl_buf b, float* out);
cl_result cl_buf_write_f64(cl_buf b, double v); cl_result cl_buf_read_f64(cl_buf b, double* out);
cl_result cl_buf_write_str(cl_buf b, cl_str s); cl_result cl_buf_read_str(cl_buf b, char* buf, size_t cap, size_t* out_len);
cl_result cl_buf_write_bytes(cl_buf b, const void* p, size_t n);
cl_result cl_buf_read_bytes(cl_buf b, void* p, size_t n, size_t* out);
Numbers are big-endian, strings are a varint length plus UTF-8, as in the protocol. Reading past the end returns an error.
Bindings
| C | C++ | Lua |
|---|---|---|
cl_payload_register(&d, &h) |
clight::net::on_payload("my_mod:ping", [](clight::Entity sender, const clight::Buf& data) { ... }, {.dir, .max_size}) (the lambda lives until unload) |
cl.net.payload_register{ id = "my_mod:ping", handler = function(sender, data) ... end } |
cl_net_send_to_server(id, b) |
clight::net::send_to_server(id, b) |
cl.net.send_to_server(id, b) |
cl_net_send_to_player(p, id, b) |
p.net_send_to_player(id, b), clight::net::send_to_player(p, id, b) |
p:send_to_player(id, b), cl.net.send_to_player(p, id, b) |
cl_buf_create(n) |
clight::net::buf_create(n) → clight::Buf (move-only, released for you) |
cl.buf.create(n) |
cl_buf_write_i32(b, v) |
b.write_i32(v) |
b:write_i32(v) |
cl_buf_read_i32(b, &v) |
b.read_i32() → Expected<int32_t> |
b:read_i32() |
A Lua payload registration repeated by a hot reload keeps the channel and swaps in the new handler.
Example: the client asks, the server answers (Lua)
cl.on("lifecycle.register", function()
cl.net.payload_register{ id = "my_mod:ask_time", handler = function(sender, data)
if sender then -- on the server
local reply = cl.buf.create(8)
reply:write_i64(sender.level:day_time())
sender:send_to_player("my_mod:ask_time", reply)
else -- on the client
cl.client.action_bar("Day time: " .. data:read_i64())
end
end }
end)
-- client: a keybind sends the question
-- cl.net.send_to_server("my_mod:ask_time", cl.buf.create(0))
Planned
Sending to every player tracking an entity, in a chunk or a level, and to everyone; can_send checks (whether the peer registered the channel); typed payload codecs; item stacks, text, positions and UUIDs in buffers; configuration-phase payloads; the CLight handshake that compares mod lists under each mod’s sync rule (Multiplayer sync and the handshake); listeners that observe or cancel vanilla packets (packets.raw).