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游戏开发

Download history 53/week @ 2024-07-01 133/week @ 2024-07-29 14/week @ 2024-08-05

每月147 次下载
bevy_stardust_extras 中使用

MIT/Apache

74KB
1K SLoC

✨ bevy_stardust

Stardust 是一个为 Bevy 构建的灵活的网络包,专注于可扩展性和并行性。

License Bevy version crates.io docs.rs

为什么选择 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_stardustbevy_stardust_extras中。

描述
bevy_stardust_quic QUIC传输层
bevy_stardust_voip 语音聊天插件
bevy_stardust_replicate 状态复制插件

许可证

bevy_stardust是免费和开源软件。它遵循

任选其一。

除非您明确声明,否则您有意提交的任何贡献,根据Apache-2.0许可证的定义包含在作品中,将按照上述方式双重许可,不附加任何额外条款或条件。

依赖项

~22MB
~405K SLoC