20 releases (9 breaking)
0.10.0 | Apr 11, 2024 |
---|---|
0.9.2 | Mar 8, 2024 |
0.9.1 | Feb 23, 2024 |
0.5.3 | Dec 19, 2023 |
0.5.1 | Nov 29, 2023 |
#79 in Game dev
126 downloads per month
Used in bevy_simplenet_events
140KB
2K
SLoC
Bevy Simplenet
Provides a bi-directional server/client channel implemented over websockets. This crate is suitable for user authentication, talking to a matchmaking service, communicating between micro-services, games that don't have strict latency requirements, etc.
- Client/server channel includes one-shot messages and a request/response API.
- Client message statuses can be tracked.
- Clients automatically work on native and WASM targets.
- Clients can be authenticated by the server (WIP).
- Provides optional server TLS.
Check out the example for a demonstration of how to build a Bevy client using this crate. NOTE: The example is currently broken until bevy_lunex
updates to Bevy v0.13.
Check out bevy_simplenet_events for an event-based framework for networking that builds on this crate.
This crate requires nightly rust.
Features
default
: includesbevy
,client
,server
featuresbevy
: derivesResource
onClient
andServer
client
: enables clients (native and WASM targets)server
: enables servers (native-only targets)tls-rustls
: enables TLS for servers viarustls
tls-openssl
: enables TLS for servers viaOpenSSL
WASM
On WASM targets the client backend will not update while any other tasks are running. You must either build an IO-oriented application that naturally spends a lot of time polling tasks, or manually release the main thread periodically (e.g. with web_sys::Window::set_timeout_with_callback_and_timeout_and_arguments_0()
). For Bevy apps the latter happens automatically at the end of every app update/tick (see the bevy::app::ScheduleRunnerPlugin
implementation).
Usage notes
- Servers and clients must be created with enfync runtimes. The backend is ezsockets.
- A client's
AuthRequest
type must match the corresponding server'sAuthenticator
type. - Client ids are defined by clients via their
AuthRequest
when connecting to a server. This means multiple sessions from the same client will have the same session id. Connections will be rejected if an id is already connected. - Client connect messages will be cloned for all reconnect attempts, so they should be treated as static data.
- Server or client messages may fail to send if the underlying connection is broken. Clients can use the signals returned from
Client::send()
andClient::request()
to track the status of a message. Client request results will always be emitted byClient::next()
. Message tracking is not available for servers. - Tracing levels assume the server is trusted and clients are not trusted.
Example
Setup
Common
Define a channel.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestConnectMsg(pub String);
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerMsg(pub u64);
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerResponse(pub u64);
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientMsg(pub u64);
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientRequest(pub u64);
#[derive(Debug, Clone)]
pub struct TestChannel;
impl ChannelPack for TestChannel
{
type ConnectMsg = TestConnectMsg;
type ServerMsg = TestServerMsg;
type ServerResponse = TestServerResponse;
type ClientMsg = TestClientMsg;
type ClientRequest = TestClientRequest;
}
Server
Prepare to make servers.
type TestServerEvent = ServerEventFrom<TestChannel>;
fn server_factory() -> ServerFactory<TestChannel>
{
// It is recommended to make server/client factories with baked-in protocol versions (e.g.
// with env!("CARGO_PKG_VERSION")).
ServerFactory::<TestChannel>::new("test")
}
Make a server and insert it into an app.
fn setup_server(mut commands: Commands)
{
let server = server_factory().new_server(
enfync::builtin::native::TokioHandle::default(),
"127.0.0.1:0",
AcceptorConfig::Default,
Authenticator::None,
ServerConfig::default(),
);
commands.insert_resource(server);
}
Client
Prepare to make clients.
type TestClientEvent = ClientEventFrom<TestChannel>;
fn client_factory() -> ClientFactory<TestChannel>
{
// You must use the same protocol version string as the server factory.
ClientFactory::<TestChannel>::new("test")
}
Make a client and insert it into an app.
fn setup_client(mut commands: Commands)
{
let client_id = 0u128;
let client = client_factory().new_client(
enfync::builtin::Handle::default(), //automatically selects native/WASM runtime
server.url(),
AuthRequest::None{ client_id },
ClientConfig::default(),
TestConnectMsg(String::from("hello"))
);
commands.insert_resource(client);
}
Sending from the client
Send a message.
fn send_client_message(client: Client<TestChannel>)
{
let message_signal = client.send(TestClientMsg(42));
}
Send a request.
fn send_client_request(client: Client<TestChannel>)
{
let request_signal = client.request(TestClientRequest(24));
}
Sending from the Server
Send a message.
fn send_server_message(server: Server<TestChannel>)
{
server.send(0u128, TestServerMsg(111));
}
Send a response.
fn send_server_response(In(token): In<RequestToken>, server: Server<TestChannel>)
{
server.respond(token, TestServerResponse(1));
}
Reading on the client
fn read_on_client(client: &mut Client<TestChannel>)
{
while let Some(client_event) = client.next()
{
match client_event
{
TestClientEvent::Report(connection_report) => match connection_report
{
ClientReport::Connected => todo!(),
ClientReport::Disconnected => todo!(),
ClientReport::ClosedByServer(reason) => todo!(),
ClientReport::ClosedBySelf => todo!(),
ClientReport::IsDead(pending_requests) => todo!(),
}
TestClientEvent::Msg(message) => todo!(),
TestClientEvent::Response(response, request_id) => todo!(),
TestClientEvent::Ack(request_id) => todo!(),
TestClientEvent::Reject(request_id) => todo!(),
TestClientEvent::SendFailed(request_id) => todo!(),
TestClientEvent::ResponseLost(request_id) => todo!(),
}
}
}
Reading on the server
fn read_on_server(server: &mut Server<TestChannel>)
{
while let Some((session_id, server_event)) = server.next()
{
match server_event
{
TestServerEvent::Report(connection_report) => match connection_report
{
ServerReport::Connected(env, message) => todo!(),
ServerReport::Disconnected => todo!(),
}
TestServerEvent::Msg(message) => todo!(),
TestServerEvent::Request(token, request) => todo!(),
}
}
}
TODOs
- Fix linker errors when the
bevy/dynamic_linking
feature is enabled. - Implement
AuthToken
for client/server authentication. - Add server shut down procedure.
- Use const generics to bake protocol versions into
Server
andClient
directly, instead of relying on factories (currently blocked by lack of robust compiler support). - Move to stable rust once
HashMap::extract_if()
is stabilized.
Bevy compatability
bevy | bevy_simplenet |
---|---|
0.13 | v0.9.0 - master |
0.12 | v0.5.0 - v0.8.0 |
0.11 | v0 - v0.4.0 |
Dependencies
~5–23MB
~333K SLoC