Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: tombi-toml/setup-tombi@9fdb1f12707aafdc140839c60421336d4ae157ea
- uses: tombi-toml/setup-tombi@23af0f0e4fcad609f048a37fb312714cd6b07741
with:
version: 1.2.6
version: 1.4.0
- run: tombi lint
- run: tombi fmt --check
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ See `README.md` for an overview of this Rust crate.

# Checking

When checking code, use `cargo clippy --workspace --all-features --all-targets` instead of `cargo check`.
Use `just prepare` to apply any automatic fixes like typos, formatting. Use `just check` to run the same checks as CI. Always use `cargo clippy` instead of `cargo check`.

# Updating

Expand Down
51 changes: 9 additions & 42 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,14 @@ keywords = ["bevy", "gamedev", "network"]
categories = ["game-development", "network-programming"]

[workspace.dependencies]
aeronet = {
path = "crates/aeronet",
version = "0.21.0",
default-features = false
}
aeronet = { path = "crates/aeronet", version = "0.21.0", default-features = false }
aeronet_channel = { path = "crates/aeronet_channel", version = "0.21.0" }
aeronet_io = {
path = "crates/aeronet_io",
version = "0.21.0",
default-features = false
}
aeronet_replicon = {
path = "crates/aeronet_replicon",
version = "0.21.0",
default-features = false
}
aeronet_tokio_runtime = {
path = "crates/aeronet_tokio_runtime",
version = "0.21.0"
}
aeronet_transport = {
path = "crates/aeronet_transport",
version = "0.21.0",
default-features = false
}
aeronet_websocket = {
path = "crates/aeronet_websocket",
version = "0.21.0",
default-features = false
}
aeronet_webtransport = {
path = "crates/aeronet_webtransport",
version = "0.21.0"
}
aeronet_io = { path = "crates/aeronet_io", version = "0.21.0", default-features = false }
aeronet_replicon = { path = "crates/aeronet_replicon", version = "0.21.0", default-features = false }
aeronet_tokio_runtime = { path = "crates/aeronet_tokio_runtime", version = "0.21.0" }
aeronet_transport = { path = "crates/aeronet_transport", version = "0.21.0", default-features = false }
aeronet_websocket = { path = "crates/aeronet_websocket", version = "0.21.0", default-features = false }
aeronet_webtransport = { path = "crates/aeronet_webtransport", version = "0.21.0" }
anyhow = { version = "1.0.97", default-features = false }
arbitrary = { version = "1.3.2", features = ["derive"] }
base64 = { version = "0.22.1" }
Expand Down Expand Up @@ -78,11 +52,7 @@ octs = { version = "1.0.0", default-features = false }
oneshot = { version = "0.1.11" }
rcgen = { version = "0.13.1" }
ringbuf = { version = "0.4.8", default-features = false }
rustls = {
version = "0.23.23",
default-features = false,
features = ["logging", "std", "tls12"]
}
rustls = { version = "0.23.23", default-features = false, features = ["logging", "std", "tls12"] }
rustls-native-certs = { version = "0.8.0" }
serde = { version = "1.0.219", features = ["derive"] }
size_format = { version = "1.0.2" }
Expand Down Expand Up @@ -120,7 +90,4 @@ unwrap_used = "warn"

[workspace.lints.rust]
missing_docs = "warn"
unexpected_cfgs = {
level = "warn",
check-cfg = ["cfg(fuzzing)", "cfg(docsrs_aeronet)"]
}
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(fuzzing)", "cfg(docsrs_aeronet)"] }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ cargo run --example iroh_peer -- --remote ENDPOINT

Uses Steam's [networking sockets](https://partner.steamgames.com/doc/api/ISteamnetworkingSockets)

You will need Steam running locally on your machine to be able to run the examples. If you want to test out peer-to-peer connections (not socket address connections), you will need to run two separate Steam clients using two separate Steam accounts - see *Development Environment* for an easy way to achieve this.
You will need Steam running locally on your machine to be able to run the examples. If you want to test out peer-to-peer connections (not socket address connections), you will need to run two separate Steam clients using two separate Steam accounts - see *Development Environment* for an easy way to achieve this.

```sh
# run a server which listens on a socket address
Expand Down
1 change: 1 addition & 0 deletions crates/aeronet/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Version changelog.
- Changed `SessionRequest` to be an entity event triggered on the server when it receives a new session request, instead of a global event (requiring a global observer)
- Made `aeronet_websocket`'s rustls crypto provider selectable while keeping AWS-LC enabled by default
- `aeronet_websocket` no longer installs a crypto provider when its plugins are added; applications must install their selected provider themselves when both `ring` and `aws-lc-rs` are enabled (or when the default `aws-lc-rs` feature is disabled), otherwise TLS configuration will panic
- Allow using dedicated Steam server sockets for `aeronet_steam`

# 0.21.0

Expand Down
5 changes: 5 additions & 0 deletions crates/aeronet_steam/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ name = "steam_server"
path = "examples/steam_server.rs"
required-features = ["server"]

[[example]]
name = "steam_dedicated_server"
path = "examples/steam_dedicated_server.rs"
required-features = ["server"]

[dependencies]
aeronet_io = { workspace = true }
anyhow = { workspace = true }
Expand Down
6 changes: 3 additions & 3 deletions crates/aeronet_steam/examples/steam_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use {
connection::{Disconnect, DisconnectReason, Disconnected},
},
aeronet_steam::{
SessionConfig, SteamworksClient,
SessionConfig, SteamworksSockets,
client::{SteamNetClient, SteamNetClientPlugin},
},
bevy::prelude::*,
Expand All @@ -28,8 +28,8 @@ fn main() -> AppExit {
steam.networking_utils().init_relay_network_access();

App::new()
.insert_resource(SteamworksClient(steam))
.add_systems(PreUpdate, |steam: Res<SteamworksClient>| {
.insert_resource(SteamworksSockets::Client(steam))
.add_systems(PreUpdate, |steam: Res<SteamworksSockets>| {
steam.run_callbacks();
})
.add_plugins((DefaultPlugins, EguiPlugin::default(), SteamNetClientPlugin))
Expand Down
189 changes: 189 additions & 0 deletions crates/aeronet_steam/examples/steam_dedicated_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//! Example dedicated server using Steam, which listens for clients sending
//! strings and sends back a string reply.
//!
//! Unlike [`steam_server`], this example logs on to Steam as a dedicated game
//! server, so it must be run with a Steam app ID that is configured for
//! dedicated server hosting. The app ID is provided programmatically below by
//! setting the `SteamAppId`/`SteamGameId` env vars (the same trick
//! [`steamworks::Client::init_app`] uses) instead of relying on a
//! `steam_appid.txt` file.
//!
//! The IO layer is identical to the regular server: the only difference is
//! that the [`SteamworksSockets`] resource wraps a [`steamworks::Server`]
//! instead of a [`steamworks::Client`].
//!
//! [`steam_server`]: ./steam_server.rs

cfg_if::cfg_if! {
if #[cfg(target_family = "wasm")] {
fn main() {
panic!("not supported on WASM");
}
} else {

use {
aeronet_io::{
Session, SessionEndpoint,
connection::{DisconnectReason, Disconnected, LocalAddr},
server::{Closed, Server},
},
aeronet_steam::{
SessionConfig, SteamworksSockets,
server::{
ListenTarget, SessionRequest, SessionResponse, SteamNetServer, SteamNetServerPlugin,
},
},
bevy::{log::LogPlugin, prelude::*},
core::net::{Ipv4Addr, SocketAddr},
std::env,
};

fn main() -> AppExit {
// `steamworks::Server::init` has no `init_app` helper, but it determines the
// app ID from the `SteamAppId`/`SteamGameId` env vars, falling back to a
// `steam_appid.txt` in the working dir only if both are unset. Set them here
// (Spacewar, 480) so the example works without any `steam_appid.txt` file.
// SAFETY: single-threaded before process startup; setting these env vars
// mirrors exactly what `steamworks::Client::init_app` does.
unsafe {
std::env::set_var("SteamAppId", "480");
std::env::set_var("SteamGameId", "480");
}

let (server, server_callbacks) = steamworks::Server::init(
Ipv4Addr::LOCALHOST,
25572,
27016,
steamworks::ServerMode::AuthenticationAndSecure,
"1.0.0.0",
)
.expect("failed to initialize dedicated server");

server.set_game_description("Description");
server.set_mod_dir("spacewar");
server.set_product("spacewar");
server.set_map_name("island");
server.set_max_players(16);
server.set_server_name("Aeronet server");
server.set_dedicated_server(true);
server.log_on_anonymous();
server.enable_heartbeats(true);

server_callbacks.networking_utils().init_relay_network_access();

let steam_id = server.steam_id();
info!("dedicated server steam ID: {steam_id:?}");

// Note: `Server::init` also returns a `steamworks::Client` that shares the
// same underlying handle, so running callbacks on the `Server` covers the
// same callback pump. We only need to insert the `SteamworksSockets`
// resource, which handles both the IO layer and running callbacks.
App::new()
.insert_resource(SteamworksSockets::Server(server))
.add_systems(PreUpdate, |steam: Res<SteamworksSockets>| {
steam.run_callbacks();
})
.add_plugins((MinimalPlugins, LogPlugin::default(), SteamNetServerPlugin))
.add_systems(Startup, open_server)
.add_systems(Update, reply)
.add_observer(on_opened)
.add_observer(on_closed)
.add_observer(on_session_request)
.add_observer(on_connecting)
.add_observer(on_connected)
.add_observer(on_disconnected)
.run()
}

fn open_server(mut commands: Commands) {
let target = match env::args().nth(1).as_deref() {
Some("addr") => ListenTarget::Addr(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 27015)),
Some("peer") => ListenTarget::Peer { virtual_port: 0 },
_ => panic!("must specify either `addr` or `peer` argument on command line"),
};

commands
.spawn_empty()
.queue(SteamNetServer::open(SessionConfig::default(), target));
}

fn on_opened(trigger: On<Add, Server>, servers: Query<&LocalAddr>) {
let server = trigger.event_target();
if let Ok(local_addr) = servers.get(server) {
info!("{server} opened on {:?}", **local_addr);
} else {
info!("{server} opened for peer connections");
}
}

fn on_closed(trigger: On<Closed>) {
panic!("server closed: {:?}", trigger.event());
}

fn on_session_request(mut request: On<SessionRequest>, clients: Query<&ChildOf>) {
let client = request.event_target();
let Ok(&ChildOf(server)) = clients.get(client) else {
return;
};

info!(
"{client} connecting to {server} with Steam ID {:?}",
request.steam_id
);
request.respond(SessionResponse::Accepted);
}

fn on_connecting(trigger: On<Add, SessionEndpoint>, clients: Query<&ChildOf>) {
let client = trigger.event_target();
let Ok(&ChildOf(server)) = clients.get(client) else {
return;
};

info!("{client} connecting to {server}");
}

fn on_connected(trigger: On<Add, Session>, clients: Query<&ChildOf>) {
let client = trigger.event_target();
let Ok(&ChildOf(server)) = clients.get(client) else {
return;
};

info!("{client} connected to {server}");
}

fn on_disconnected(trigger: On<Disconnected>, clients: Query<&ChildOf>) {
let client = trigger.event_target();
let Ok(&ChildOf(server)) = clients.get(client) else {
return;
};

match &trigger.reason {
DisconnectReason::ByUser(reason) => {
info!("{client} disconnected from {server} by user: {reason}");
}
DisconnectReason::ByPeer(reason) => {
info!("{client} disconnected from {server} by peer: {reason}");
}
DisconnectReason::ByError(err) => {
warn!("{client} disconnected from {server} due to error: {err:#}");
}
}
}

fn reply(mut clients: Query<(Entity, &mut Session), With<ChildOf>>) {
for (client, mut session) in &mut clients {
// explicit deref so we can access disjoint fields
let session = &mut *session;
for packet in session.recv.drain(..) {
let msg =
String::from_utf8(packet.payload.into()).unwrap_or_else(|_| "(not UTF-8)".into());
info!("{client} > {msg}");

let reply = format!("You sent: {msg}");
info!("{client} < {reply}");
session.send.push(reply.into());
}
}
}

}}
6 changes: 3 additions & 3 deletions crates/aeronet_steam/examples/steam_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use {
server::{Closed, Server},
},
aeronet_steam::{
SessionConfig, SteamworksClient,
SessionConfig, SteamworksSockets,
server::{
ListenTarget, SessionRequest, SessionResponse, SteamNetServer, SteamNetServerPlugin,
},
Expand All @@ -30,8 +30,8 @@ fn main() -> AppExit {
steam.networking_utils().init_relay_network_access();

App::new()
.insert_resource(SteamworksClient(steam))
.add_systems(PreUpdate, |steam: Res<SteamworksClient>| {
.insert_resource(SteamworksSockets::Client(steam))
.add_systems(PreUpdate, |steam: Res<SteamworksSockets>| {
steam.run_callbacks();
})
.add_plugins((MinimalPlugins, LogPlugin::default(), SteamNetServerPlugin))
Expand Down
8 changes: 4 additions & 4 deletions crates/aeronet_steam/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use {
crate::{
SteamworksClient,
SteamworksSockets,
config::SessionConfig,
session::{SessionError, SteamNetIo, SteamNetSessionPlugin, entity_to_user_data},
},
Expand Down Expand Up @@ -82,8 +82,8 @@ impl SteamNetClient {
/// Creates an [`EntityCommand`] to set up a session and connect it to the
/// `target`.
///
/// [`SteamworksClient`] must be present in the world before this command is
/// applied.
/// [`SteamworksSockets`] must be present in the world before this command
/// is applied.
///
/// # Examples
///
Expand Down Expand Up @@ -120,7 +120,7 @@ fn connect(mut entity: EntityWorldMut, config: SessionConfig, target: ConnectTar
let mtu = config.send_buffer_size;
let sockets = entity
.world()
.resource::<SteamworksClient>()
.resource::<SteamworksSockets>()
.networking_sockets();
let (tx_next, rx_next) = oneshot::channel::<ConnectResult>();
blocking::unblock(move || {
Expand Down
Loading