7 个版本 (4 个重大变更)
0.4.0 | 2024 年 7 月 5 日 |
---|---|
0.3.0 | 2024 年 2 月 23 日 |
0.2.0 | 2024 年 2 月 1 日 |
0.1.2 | 2024 年 1 月 19 日 |
0.0.1 | 2024 年 1 月 2 日 |
#316 in 游戏开发
每月下载 26 次
81KB
1.5K SLoC
Bevy Simplenet Events
提供基于事件的 API,用于处理网络连接,建立在 bevy_simplenet
之上。
此包需要 nightly rust。
使用说明
- 客户端连接事件、客户端/服务器消息事件和服务器响应可以通过多个系统中的事件读取器进行迭代。客户端请求可以通过一个系统中的
ServerRequestSource
进行排空。 - 单个类型的事件“通道”是 FIFO,但是不同的事件通道之间不会相互同步。此包不适合需要所有客户端/服务器输出全局 FIFO 排序的用户(直接使用
bevy_simplenet
)。 - 我们假设用户的连接事件处理器在调度
RefreshSet
的First
之后和在其他事件处理器之前调度。 - 事件必须在服务器和客户端上以相同的顺序注册。
同步保证
此包的 API 极具意见性,以方便精确处理重连。
我们在调度 First
的 RefreshSet
中每帧更新客户端和服务器状态。所有旧事件都被清除,并插入新事件。如果用户的连接事件处理器如预期的那样在其他事件处理器之前调度,那么我们保证以下内容
客户端
ClientMessageReader
只会从当前连接会话中读取服务器消息。旧消息(在最后断开之前)将被丢弃。ClientResponseReader
只会发出在当前连接会话中接收到的响应,即ServerResponse::Response
或ServerResponse::Ack
。所有其他响应将失败,并显示一个响应失败变体(拒绝/发送失败/响应丢失)。请注意,我们保证对于每个发送的客户端请求,都会发出某种类型的响应。- 如果最新的
ClientReport::Connected
没有被ClientConnectionReader
至少读取一次(TODO:存在上游竞争条件),或者客户端未连接,则客户端消息/请求将无法发送或出现错误。可以使用从EventClient::send
返回的MessageSignal
监控消息状态,并使用从EventClient::request
返回的RequestSignal
监控请求状态;或者您也可以等待一个作为事件发出的结果。我们包括这个保证,以减少在处理连接事件时,客户端基于过时状态发送消息的可能性。
服务器
ServerMessageReader
和ServerRequestSource
只会从客户端的当前连接会话中读取客户端消息和请求。旧消息(在最后一次断开之前)将被丢弃。- 如果对于该客户端的最新
ServerReport::Connected
没有被ServerConnectionReader
至少读取一次(TODO:存在上游竞争条件),或者客户端未连接,则服务器向客户端发送的消息将无法发送或出现错误。我们包括这个保证,以减少在处理连接事件时,服务器基于过时状态发送消息的可能性。注意,来自旧连接会话的响应始终无法发送到新会话。
性能
该软件包比 bevy_simplenet
效率低。
- 事件被序列化和反序列化两次,以启用临时事件类型。
- 客户端和服务器有额外的间接和复制,以便将消息从内部客户端/服务器传输到用户。
- 事件通过引用而不是值暴露(除了客户端请求,在服务器上按值排出)。
创建一个频道
共享
准备消息类型和实现 EventPack
的频道标签。
#[derive(SimplenetEvent, Serialize, Deserialize)]
struct DemoMsg1(usize);
#[derive(SimplenetEvent, Serialize, Deserialize)]
struct DemoMsg2(usize);
#[derive(SimplenetEvent, Serialize, Deserialize)]
struct DemoRequest(usize);
#[derive(SimplenetEvent, Serialize, Deserialize)]
struct DemoResponse(usize);
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
struct DemoConnectMsg(String);
#[derive(Debug, Clone)]
struct DemoChannel;
impl EventPack for DemoChannel
{
type ConnectMsg = DemoConnectMsg;
}
准备事件设置函数。这应该在服务器和客户端应用上调用。
fn event_setup(app: &mut App)
{
app
.register_simplenet_client_message::<DemoChannel, DemoMsg1>()
.register_simplenet_client_message::<DemoChannel, DemoMsg2>()
.register_simplenet_server_message::<DemoChannel, DemoMsg1>()
.register_simplenet_server_message::<DemoChannel, DemoMsg2>()
.register_simplenet_request_response::<DemoChannel, DemoRequest, DemoResponse>()
;
}
服务器
准备服务器工厂。
type DemoServerReport = bevy_simplenet::ServerReport<DemoConnectMsg>;
fn demo_server_factory() -> bevy_simplenet::ServerFactory<EventWrapper<DemoChannel>>
{
bevy_simplenet::ServerFactory::<EventWrapper<DemoChannel>>::new("test")
}
准备服务器设置函数(示例)。
fn setup_server(app: &mut App) -> url::Url
{
let server = demo_server_factory().new_server(
enfync::builtin::native::TokioHandle::adopt_or_default(),
"127.0.0.1:0",
bevy_simplenet::AcceptorConfig::Default,
bevy_simplenet::Authenticator::None,
bevy_simplenet::ServerConfig::default(),
);
let url = server.url();
app.insert_simplenet_server(server);
event_setup(app);
url
}
客户端
准备客户端工厂。
fn demo_client_factory() -> bevy_simplenet::ClientFactory<EventWrapper<DemoChannel>>
{
bevy_simplenet::ClientFactory::<EventWrapper<DemoChannel>>::new("test")
}
准备客户端设置函数(示例)。
fn setup_client(app: &mut App, url: url::Url, client_id: SessionId, connect_msg: DemoConnectMsg)
{
let client = demo_client_factory().new_client(
enfync::builtin::Handle::adopt_or_default(),
url,
bevy_simplenet::AuthRequest::None{ client_id },
bevy_simplenet::ClientConfig::default(),
connect_msg
);
app.insert_simplenet_client(client);
event_setup(app);
}
客户端连接处理
每个tick必须先处理客户端连接报告,然后再处理其他所有客户端事件。
fn handle_client_connection_reports(reader: ClientConnectionReader<DemoChannel>)
{
for connection in reader.iter()
{
match connection
{
bevy_simplenet::ClientReport::Connected => todo!(),
bevy_simplenet::ClientReport::Disconnected => todo!(),
bevy_simplenet::ClientReport::ClosedByServer(_) => todo!(),
bevy_simplenet::ClientReport::ClosedBySelf => todo!(),
bevy_simplenet::ClientReport::IsDead(_) => todo!(),
}
}
}
服务器连接处理
每个tick必须先处理服务器连接报告,然后再处理其他所有服务器事件。
fn handle_server_connection_reports(reader: ServerConnectionReader<DemoChannel>)
{
for (session_id, connection) in reader.iter()
{
match connection
{
bevy_simplenet::ServerReport::<DemoConnectMsg>::Connected(_, _) => todo!(),
bevy_simplenet::ServerReport::<DemoConnectMsg>::Disconnected => todo!(),
}
}
}
从客户端发送
可以发送任何已注册的消息类型。
fn send_client_message(client: EventClient<DemoChannel>)
{
client.send(DemoMsg1(42));
client.send(DemoMsg2(24));
}
从服务器发送
可以发送任何已注册的消息类型。
fn send_server_message(In(session_id): In<SessionId>, server: EventServer<DemoChannel>)
{
server.send(session_id, DemoMsg1(42)).unwrap();
server.send(session_id, DemoMsg2(24)).unwrap();
}
服务器端读取
客户端消息
fn read_client_messages(reader: ServerMessageReader<DemoChannel, DemoMsg1>)
{
for (session_id, message) in reader.iter()
{
todo!()
}
}
客户端请求
清空请求源会消耗所有请求,因为我们期望你对请求令牌执行某些操作。
fn read_client_requests(source: ServerRequestSource<DemoChannel, DemoRequest1, DemoResponse1>)
{
for (token, request) in source.drain()
{
todo!()
}
}
客户端读取
服务器消息
fn read_server_messages(reader: ClientMessageReader<DemoChannel, DemoMsg1>)
{
for message in reader.iter()
{
todo!()
}
}
服务器响应
fn read_server_responses(reader: ClientResponseReader<DemoChannel, DemoRequest1, DemoResponse1>)
{
for response in reader.iter()
{
match response
{
ServerResponse::Response(response, _) => todo!(),
ServerResponse::Ack(_) => todo!(),
ServerResponse::Reject(_) => todo!(),
ServerResponse::SendFailed(_) => todo!(),
ServerResponse::ResponseLost(_) => todo!(),
}
}
}
Bevy 兼容性
bevy | bevy_simplenet_events |
---|---|
0.14 | v0.4 |
0.13 | v0.3 |
0.12 | v0.1 - v0.2 |
依赖项
~24–37MB
~618K SLoC