1 个不稳定版本
0.1.0 | 2024年7月5日 |
---|
#9 在 #generate-typescript
每月26次下载
63KB
1.5K SLoC
bufferfish
bufferfish
是一个在Rust和TypeScript之间处理二进制网络消息(如通过WebSockets)的实用库。它提供了一个简单的API来将数据编码和解码成二进制数组,以及从你的Rust代码生成TypeScript定义和解码函数。
此库具有不稳定的API,可能缺少一些基本功能。我虽然在自己的生产项目中使用它,但并不推荐在生产环境中使用。
目录
入门
使用 cargo add bufferfish
将Rust库添加到项目中。
使用 npm install bufferfish
将TypeScript库添加到项目中。
示例
use bufferfish::{Encode};
use futures_util::SinkExt;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::{accept_async, tungstenite::Message};
#[derive(Encode)]
#[repr(u16)]
enum PacketId {
Join,
}
// We need to make sure we can convert our enum to a u16, as that is the type
// `bufferfish` uses to identify packets. You can use the `num_enum` crate and
// derive `IntoPrimitive` and `FromPrimitive` to remove this step completely.
impl From<PacketId> for u16 {
fn from(id: PacketId) -> u16 {
match id {
PacketId::Join => 0,
}
}
}
// We annotate our packet with the #[Encode] macro to enable automatic
// encoding and decoding to or from a `Bufferfish`.
//
// Additionally, we use the #[bufferfish] attribute to specify the packet ID.
#[derive(Encode)]
#[bufferfish(PacketId::Join)]
struct JoinPacket {
id: u32
username: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:3000").await?;
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(async move {
if let Err(e) = process(stream).await {
eprintln!("Error processing connection: {}", e);
}
});
}
Ok(())
}
async fn process(steam: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
let mut ws = accept_async(steam).await?;
let packet = JoinPacket {
id: 1,
username: "Rob".to_string(),
};
let bf = packet.to_bufferfish()?;
ws.send(Message::Binary(bf.into())).await?;
Ok(())
}
使用生成的解码函数(JavaScript)
const ws = new WebSocket("ws://127.0.0.1:3000")
ws.binaryType = "arraybuffer"
ws.onmessage = (event) => {
const bf = new Bufferfish(event.data)
const packetId = bf.readUint16()
if (packetId === PacketId.Join) {
const packet = decodeJoinPacket(bf)
console.log(packet) // { id: 1, username: "Rob" }
}
}
手动解码Bufferfish(JavaScript)
const ws = new WebSocket("ws://127.0.0.1:3000")
ws.binaryType = "arraybuffer"
ws.onmessage = (event) => {
const bf = new Bufferfish(event.data)
const packetId = bf.readUint16()
if (packetId === PacketId.Join) {
const id = bf.readUint32()
const username = bf.readString()
console.log({
id,
username,
}) // { id: 1, username: "Rob" }
}
}
TypeScript代码生成
bufferfish
提供了一个 generate
函数,可以在 build.rs
(或用于CLI脚本,由服务器在启动时调用等)中使用,以从你的Rust代码生成TypeScript定义和函数,这意味着你的Rust服务器成为所有网络消息的真理来源,并减少了客户端手动与 bufferfish
交互。
// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=build.rs");
bufferfish::generate("src", "../client/src/generated/Packet.ts")?;
Ok(())
}
代码生成示例
use bufferfish::Encode;
pub enum PacketId {
Join = 0,
Leave,
Unknown = 255,
}
#[derive(Encode)]
#[bufferfish(PacketId::Join)]
pub struct JoinPacket {
pub id: u8,
pub username: String,
}
#[derive(Encode)]
#[bufferfish(PacketId::Leave)]
pub struct LeavePacket;
/* AUTOGENERATED BUFFERFISH FILE, DO NOT EDIT */
import { Bufferfish } from 'bufferfish';
export enum PacketId {
Join = 0,
Leave = 1,
Unknown = 255,
}
export interface JoinPacket {
id: number
username: string
}
export const decodeJoinPacket = (bf: Bufferfish): JoinPacket => {
return {
id: bf.readUint8() as number
username: bf.readString() as string
}
}
可编码/可解码类型
支持类型 | 解码为 |
---|---|
u8 |
数字 |
u16 |
数字 |
u32 |
数字 |
i8 |
数字 |
i16 |
数字 |
i32 |
数字 |
bool |
布尔值 |
字符串 |
字符串 |
Vec<T> 其中T:可编码 |
数组<T> |
T其中T:可编码 |
object 或原始类型 |
解码时相反。
说明
- 我建议使用 num_enum crate 对你希望进行
Encode
的枚举进行IntoPrimitive
和FromPrimitve
的推导。这可以消除大量的样板代码。 - 在TypeScript中,枚举(Enums)经常被提及为“不好”的特性,这在考虑典型的Web开发用例时通常是正确的。然而,在将“操作码”列表映射到对开发人员友好的名称的情况下,它们实际上非常有用。现代打包器——例如
esbuild
—— 实际上可以将其内联,这意味着我们最终输出中只有整数字面量。。
安全
bufferfish
函数确保输入在“尽最大努力”的情况下是有效的。内部缓冲区使用最大容量 (默认为1024字节) 进行构建,如果输入会导致内部缓冲区超过该阈值,则会失败构建。
在读取数据时,您总是会得到正确的返回类型——然而,如果输入不正确但技术上有效,您仍然会受到损坏数据的影响。例如,如果您在一个包含在光标位置处的 u16
的缓冲区上调用 read_u8
,您将得到一个 u8
,因为缓冲区无法知道它最初是作为 u16
编码的。这是有效数据,但很可能是一个意外的值。
在操作缓冲区之前应该处理这类问题。
通过 Decode
特性解码过大的 bufferfish
将仅忽略/丢弃额外的数据,因为它只会读取由 Encodable
实现生成的特定字节长度。
解码过小的 bufferfish
将返回一个 BufferfishError::FailedWrite
。
贡献
bufferfish
欢迎贡献,但应注意的是,该库是为我的游戏项目创建的,我没有兴趣使其成为广泛通用的库。如果您认为某个功能请求或错误修复对其他人有用,请随时打开一个问题或PR。
许可证
bufferfish
源代码根据您的选择 dual-licensed under either
at your option。
依赖关系
~135–410KB