Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Commit

Permalink
Stardust 0.6.0 (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
Veritius authored Aug 2, 2024
1 parent 0cb0dba commit 3c78c04
Show file tree
Hide file tree
Showing 45 changed files with 2,349 additions and 901 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"rust-analyzer.cargo.features": "all",
"todo-tree.filtering.excludeGlobs": [
"Cargo.lock",
"**/target"
Expand Down
93 changes: 45 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
Stardust is a flexible networking crate built for Bevy, with a focus on extensibility and parallelism.
<br></br>

![License](https://img.shields.io/badge/license-MIT_or_Apache_2.0-green)
[![Bevy version](https://img.shields.io/badge/bevy-0.13-blue?color=blue)](https://bevyengine.org/)
[![Crates.io](https://img.shields.io/crates/v/bevy_stardust)](https://crates.io/crates/bevy_stardust)
[![License](https://img.shields.io/badge/license-MIT_or_Apache_2.0-green)](#license)
[![Bevy version](https://img.shields.io/badge/bevy-0.14-blue?color=blue)](https://bevyengine.org/)
[![crates.io](https://img.shields.io/crates/v/bevy_stardust)](https://crates.io/crates/bevy_stardust)
[![docs.rs](https://img.shields.io/docsrs/bevy_stardust)](https://docs.rs/bevy_stardust/latest/bevy_stardust/)

## Why Stardust?
### ECS-integrated
Expand All @@ -24,17 +25,10 @@ You can use any transport layer you want. Use UDP, TCP, QUIC, HTTP, some homebre

You can use any replication or extra features you want. If you prefer a specific crate for replication, it's really easy to integrate it into Stardust, as long as it has some kind of API for taking in and outputting bytes.

## Planned extensions
The following features are planned to be created as additional crates, as part of the overall project.

- Replication plugin
- UDP, QUIC, and WebTransport plugins
- Real time voice plugin

## Usage
| Bevy | Stardust |
| ---- | -------- |
| 0.13 | 0.5 |
| 0.14 | 0.6 |
| 0.12 | 0.2 |
| 0.11 | 0.1 |

Expand All @@ -45,18 +39,13 @@ The following features are planned to be created as additional crates, as part o

**A simple example project:**
```rust
// This example assumes that you don't have the reflect feature flag.
// If you do, make sure your channel types implement TypePath.
// Additionally, spawning NetworkPeer entities is handled by transport layer plugins.
// For the purpose of this example, we'll assume they magically appeared somehow.

use std::any::TypeId;
use bevy_ecs::prelude::*;
use bevy_app::{prelude::*, ScheduleRunnerPlugin, MainSchedulePlugin};
use bevy::{prelude::*, app::{ScheduleRunnerPlugin, MainSchedulePlugin}};
use bevy_stardust::prelude::*;

// Channels are accessed with types in the type system.
// Simply put, you just need to create simple types like this.
// Any type that implements Any is usable in Stardust.
// Simply put, you just need to create a field-less struct like this.
// You can use Rust's privacy system to control channel access.
struct MyChannel;

Expand All @@ -73,16 +62,9 @@ fn main() {
// Once you do this, it becomes visible in the ChannelRegistry.
// The ChannelRegistry is effectively a giant table of every registered channel.
app.add_channel::<MyChannel>(ChannelConfiguration {
// 'Reliable' messages will be detected if lost.
reliable: ReliabilityGuarantee::Reliable,

// 'Ordered' messages will be received in the same order they're sent.
ordered: OrderingGuarantee::Ordered,

// 'Fragmentable' messages will be broken up for transmission if need be.
// This is actually just a flag to say that the messages *might* need to be fragmented.
// Whether or not things are fragmented is up to the transport layer.
fragmented: true,
// Controls the reliability and ordering of messages.
// Read the documentation for MessageConsistency for a full explanation.
consistency: MessageConsistency::ReliableOrdered,

// Higher priority messages will be sent before others.
priority: 0,
Expand All @@ -99,27 +81,31 @@ fn main() {
app.add_systems(Update, (send_words_system, read_words_system));
}

// Messages use the Bytes type.
// This is cheaply clonable and you can send the same message to multiple peers.
// For this example, we create one from the bytes of a static str.
const MESSAGE: Bytes = Bytes::from_static("Hello, world!".as_bytes());
// Messages use the Message type, which is a wrapper around the Bytes type.
// This is cheaply clonable and you can send the same message to multiple peers without copying.
// Here, we simply use the from_static_str method, which is very cheap.
const MESSAGE: Message = Message::from_static_str("Hello, world!");

// Queueing messages just requires component access.
// This means you can use query filters to achieve better parallelism.
fn send_words_system(
registry: ChannelRegistry,
mut query: Query<(Entity, &mut NetworkMessages<Outgoing>), With<NetworkPeer>>
channels: Channels,
mut query: Query<(Entity, &mut PeerMessages<Outgoing>), With<Peer>>
) {
// The ChannelId must be retrieved from the registry.
// These are more friendly to store since they're just numbers.
// You can cache them if you want, as long as they aren't used in different Worlds.
let channel = registry.channel_id(TypeId::of::<MyChannel>()).unwrap();
let channel = channels.id(TypeId::of::<MyChannel>()).unwrap();

// You can also iterate in parallel, if you have a lot of things.
for (entity, mut outgoing) in query.iter_mut() {
// Bytes objects are cheaply clonable, reference counted storages.
// You can send them to as many peers as you want once created.
outgoing.push(channel, MESSAGE);
outgoing.push_one(ChannelMessage {
channel,
message: MESSAGE,
});

println!("Sent a message to {entity:?}");
}
}
Expand All @@ -128,27 +114,38 @@ fn send_words_system(
// The reading queue is a different component from the sending queue.
// This means you can read and send bytes in parallel, or in different systems.
fn read_words_system(
registry: ChannelRegistry,
query: Query<(Entity, &NetworkMessages<Incoming>), With<NetworkPeer>>
channels: Channels,
query: Query<(Entity, &PeerMessages<Incoming>), With<Peer>>
) {
let channel = registry.channel_id(TypeId::of::<MyChannel>()).unwrap();
let channel = channels.id(TypeId::of::<MyChannel>()).unwrap();
for (entity, incoming) in query.iter() {
let messages = incoming.channel_queue(channel);
for message in messages.iter() {
for message in incoming.iter_channel(channel) {
// Stardust only outputs bytes, so you need to convert to the desired type.
// Also, in real products, don't unwrap, write checks. Never trust user data.
let string = std::str::from_utf8(&*message).unwrap();
// We unwrap here for the sake of an example. In real code, you should
// program defensively, and handle error cases appropriately.
let string = message.as_str().unwrap();
println!("Received a message from {entity:?}: {string:?}");
}
}
}
```

Available feature flags:
- `reflect`: Adds `Reflect` to the `Channel` supertrait
- `hashing`: Allows hashing Stardust-related data
## Related crates
### Existing
The following crates are parts of the project that are out of scope for the `bevy_stardust` crate, and are distributed separately, such as transport layers.

| Crate | Description |
|------------------------|-----------------------------|
| `bevy_stardust_extras` | A collection of misc. tools |

### Planned
The following crates are planned to be implemented as part of the overall project, but aren't done yet. They're also too significant or too different to end up in `bevy_stardust` or `bevy_stardust_extras`.

**Please note:** The `hashing` feature flag is dependent on `gxhash`, which will not compile on targets without AES intrinsics. It's made available for local testing, but will break in production. See the [tracking issue](https://github.com/Veritius/bevy_stardust/issues/31) for more.
| Crate | Description |
|---------------------------|--------------------------|
| `bevy_stardust_quic` | QUIC transport layer |
| `bevy_stardust_voip` | Voice chat plugin |
| `bevy_stardust_replicate` | State replication plugin |

## License
bevy_stardust is free and open source software. It's licensed under:
Expand Down
24 changes: 24 additions & 0 deletions extras/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name="bevy_stardust_extras"
version="0.1.0"
edition="2021"
authors=["Veritius <[email protected]>"]
license="MIT OR Apache-2.0"
description="Miscellaneous utilities for bevy_stardust"
repository="https://github.com/veritius/bevy_stardust/"
keywords=["bevy", "gamedev", "networking"]

[dependencies.bevy]
version = "0.14"
default-features = false

[dependencies.bevy_stardust]
version = "0.6"
path = "../stardust"

[dependencies.octs]
version = "0.4.0"
optional = true

[features]
octs = ["dep:octs"]
1 change: 1 addition & 0 deletions extras/LICENSE-APACHE
1 change: 1 addition & 0 deletions extras/LICENSE-MIT
18 changes: 18 additions & 0 deletions extras/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# bevy_stardust_extras
Miscellaneous functionality that doesn't belong in `bevy_stardust`, but aren't significant enough to have its own crate. Includes various tools for testing and writing examples, as well as some tricks for encoding.

| Bevy version | Stardust version | Crate version |
|--------------|------------------|---------------|
| `0.14.0` | `0.6.0` | `0.1.0` |

## Feature flags
- `octs` - Adds implementations for traits from the `octs` crate.

## License
bevy_stardust_extras is free and open source software. It's licensed under:
* MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
5 changes: 5 additions & 0 deletions extras/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]

pub mod link;
pub mod numbers;
121 changes: 121 additions & 0 deletions extras/src/link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! A simple transport layer using inter-thread communications, intended for use in tests and examples.
//!
//! Usage is simple, just add [`LinkTransportPlugin`] to all involved apps.
//! Then, use [`pair`] to create two [`Link`] components that communicate with eachother.
//! These 'links' don't do any kind of handshake. Once added to an entity, they communicate immediately.
use std::sync::{mpsc::{channel, Receiver, Sender, TryRecvError}, Mutex};
use bevy::prelude::*;
use bevy_stardust::prelude::*;

/// Adds a simple transport plugin for apps part of the same process.
/// See the [top level documentation](self) for more information.
pub struct LinkTransportPlugin;

impl Plugin for LinkTransportPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, (recv_link_data, remove_disconnected)
.chain().in_set(NetworkRecv::Receive));

app.add_systems(PostUpdate, (send_link_data, remove_disconnected)
.chain().in_set(NetworkSend::Transmit));
}
}

/// A connection to another `Link`, made with [`pair`].
///
/// A `Link` will only communicate with its counterpart.
#[derive(Component)]
pub struct Link(SideInner);

/// Creates two connected [`Link`] objects.
pub fn pair() -> (Link, Link) {
let (left_tx, left_rx) = channel();
let (right_tx, right_rx) = channel();

let left = Link(SideInner {
sender: left_tx,
receiver: Mutex::new(right_rx),
disconnected: false,
});

let right = Link(SideInner {
sender: right_tx,
receiver: Mutex::new(left_rx),
disconnected: false,
});

return (left, right);
}

struct SideInner {
sender: Sender<ChannelMessage>,
// Makes the struct Sync, so it can be in a Component.
// Use Exclusive when it's stabilised.
receiver: Mutex<Receiver<ChannelMessage>>,
disconnected: bool,
}

fn recv_link_data(
mut query: Query<(&mut Link, &mut PeerMessages<Incoming>), With<Peer>>,
) {
query.par_iter_mut().for_each(|(mut link, mut queue)| {
let receiver = link.0.receiver.get_mut().unwrap();
loop {
match receiver.try_recv() {
Ok(message) => {
queue.push_one(message);
},

Err(TryRecvError::Empty) => { break },

Err(TryRecvError::Disconnected) => {
link.0.disconnected = true;
break;
},
}
}
});
}

fn send_link_data(
mut query: Query<(&mut Link, &PeerMessages<Outgoing>), With<Peer>>,
) {
query.par_iter_mut().for_each(|(mut link, queue)| {
let sender = &link.0.sender;
'outer: for (channel, queue) in queue {
for payload in queue {
match sender.send(ChannelMessage { channel, message: payload }) {
Ok(_) => {},
Err(_) => {
link.0.disconnected = true;
break 'outer;
},
}
}
}
});
}

fn remove_disconnected(
mut commands: Commands,
mut query: Query<(Entity, &Link, Option<&mut PeerLifestage>)>,
mut events: EventWriter<PeerDisconnectedEvent>,
) {
for (entity, link, stage) in query.iter_mut() {
if link.0.disconnected {
debug!("Link on entity {entity:?} disconnected");
commands.entity(entity).remove::<Link>();

events.send(PeerDisconnectedEvent {
peer: entity,
reason: DisconnectReason::Unspecified,
comment: None,
});

if let Some(mut stage) = stage {
*stage = PeerLifestage::Closed;
}
}
}
}
7 changes: 7 additions & 0 deletions extras/src/numbers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Types for working with numbers, such as efficient encoding and easier logic.
mod sequence;
mod varint;

pub use sequence::Sequence;
pub use varint::VarInt;
Loading

0 comments on commit 3c78c04

Please sign in to comment.