9 个版本 (5 个破坏性更新)
0.6.0 | 2024年8月2日 |
---|---|
0.5.1 | 2024年4月4日 |
0.5.0 | 2024年3月30日 |
0.4.2 | 2024年3月22日 |
0.1.0 | 2023年8月19日 |
#138 在 游戏开发
每月147 次下载
在 bevy_stardust_extras 中使用
74KB
1K SLoC
✨ bevy_stardust
Stardust 是一个为 Bevy 构建的灵活的网络包,专注于可扩展性和并行性。为什么选择 Stardust?
ECS 集成
简单来说,Stardust 只是一个插件。所有状态信息都以实体、组件和系统的方式存在于 Bevy 的 World
中。连接只是实体,您可以将任何数据附加到它们上,而无需复杂的关联数组,并且可以简单地通过查询访问它们。
Stardust 为插件提供了一级支持,确保所有 API 都可以无问题地由插件使用,并提供强大的组织工具和抽象。
并行
Stardust 专门设计成尽可能并行运行。由于一切都在 ECS 中,Bevy 调度器允许您并行运行您的网络游戏系统,而您的工作量非常小。
消息队列 API 被设计成尽可能并行,仅仅是附加到连接实体的组件。这意味着您可以为所欲为地应用查询过滤器,让不相交的访问并行执行网络操作!
模块化
Stardust 的核心不是单体,而是提供一个用于发送和读取字节的 API,以及一个用于管理连接的 API。
您可以使用任何想要的传输层。使用 UDP、TCP、QUIC、HTTP、一些自制的传输层、I2C、AM 广播,甚至是 海上信号旗,一切都可以同时进行,无需额外努力。跨平台游戏从未如此简单或灵活。
您可以使用任何想要的复制或额外功能。如果您更喜欢特定的复制工具箱,将其集成到 Stardust 中非常简单,只要它有某种类型的 API 来接收和输出字节即可。
用法
Bevy | Stardust |
---|---|
0.14 | 0.6 |
0.12 | 0.2 |
0.11 | 0.1 |
bevy_stardust
是核心 '接口' 包。它提供了编写网络代码所需的一切,但不处理互联网通信或复制等问题,这些都留给其他包。
一个简单的示例项目
use std::any::TypeId;
use bevy::{prelude::*, app::{ScheduleRunnerPlugin, MainSchedulePlugin}};
use bevy_stardust::prelude::*;
// Channels are accessed with types in the type system.
// 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;
fn main() {
let mut app = App::new();
// At the very least, Stardust needs the MainSchedulePlugin to work.
app.add_plugins((
ScheduleRunnerPlugin::default(),
StardustPlugin,
));
// Each channel needs to be added (or 'registered') to the app.
// 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 {
// 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,
});
// Any transport layers should be added after you register all channels.
// This is just a rule of thumb, though, some might not need to be.
// Make sure to check the relevant documentation.
// Your systems can be added at any point, but we'll do them here.
// Also see the scheduling types in the scheduling module for advanced usage.
// Most of the time, you just need to put things in the update schedule.
// Also, note that since these systems have disjoint accesses, they run in parallel.
app.add_systems(Update, (send_words_system, read_words_system));
}
// 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(
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 = 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_one(ChannelMessage {
channel,
message: MESSAGE,
});
println!("Sent a message to {entity:?}");
}
}
// Reading messages also just requires component accesses.
// 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(
channels: Channels,
query: Query<(Entity, &PeerMessages<Incoming>), With<Peer>>
) {
let channel = channels.id(TypeId::of::<MyChannel>()).unwrap();
for (entity, incoming) in query.iter() {
for message in incoming.iter_channel(channel) {
// Stardust only outputs bytes, so you need to convert to the desired type.
// 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:?}");
}
}
}
相关包
现有
以下包是 bevy_stardust
包的范围之外的项目的部分,它们是独立分发的,例如传输层。
包 | 描述 |
---|---|
bevy_stardust_extras |
一系列辅助工具 |
计划中
以下这些crate计划作为整体项目的一部分实现,但尚未完成。它们的重要性或差异性也太大,不能包含在bevy_stardust
或bevy_stardust_extras
中。
包 | 描述 |
---|---|
bevy_stardust_quic |
QUIC传输层 |
bevy_stardust_voip |
语音聊天插件 |
bevy_stardust_replicate |
状态复制插件 |
许可证
bevy_stardust是免费和开源软件。它遵循
- MIT许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
- Apache许可证,版本2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
任选其一。
除非您明确声明,否则您有意提交的任何贡献,根据Apache-2.0许可证的定义包含在作品中,将按照上述方式双重许可,不附加任何额外条款或条件。
依赖项
~22MB
~405K SLoC