#declarative-ui #bevy-ecs #cross-platform #ecs #ecs-architecture #bevy #dioxus

bin+lib dip

使用 Rust 和类似 React 的声明式 UI 框架以及可扩展的 ECS 架构编写跨平台应用程序

3 个不稳定版本

0.2.1 2022 年 12 月 19 日
0.2.0 2022 年 12 月 17 日
0.1.0 2022 年 3 月 10 日

1271游戏开发

每月 24 次下载

MIT/Apache

5.5MB
2.5K SLoC

dip


全 Rust Web3 应用程序工具包,专注于
基于 ECS 的事件驱动开发。

Bevy 游戏引擎提供支持。

从桌面应用程序到元宇宙。


use dip::prelude::*;

fn main() {
    App::new()
        .insert_resource(WindowDescriptor {
            title: "dip Plugin Example".to_string(),
            ..Default::default()
        })
        .add_plugin(DesktopPlugin::<NoUiState, NoUiAction, NoAsyncAction>::new(Root))
        .run();
}

fn Root(cx: Scope) -> Element {
    cx.render(rsx! {
        h1 { "Hello, World !" }
    })
}
  • 所有功能都作为 Bevy 插件实现
  • 数据驱动 ECS 设计模式
  • 在游戏、桌面应用程序和命令行工具之间共享您的逻辑
  • 基于 Webview 的 UI,由 Tauri 提供
  • 通过 Dioxus 提供类似 React 的声明式 UI
  • 开发者工具
    • 捆绑包:一键设置您的计算机的工具
      • Homebrew:通过 brew bundle 安装 Homebrew 公式、cask、mas
      • 符号链接管理器,即 Dotfiles:受 GNU Stow 启发。
      • 版本管理器:类似于 asdfnvmrbenvgvm 等。
    • 设备:与硬件加密钱包交互。

警告:dip 仍处于非常早期的开发阶段。

v0.1 完全是不同的应用程序。我想制作一个跨平台的文本编辑器,但最终制作了这个框架。

功能

桌面应用程序

具有组件状态的最小设置

代码示例
# Cargo.toml

[dependencies]
dip = { version = "0.2", features = ["desktop"] }
use dip::prelude::*;

fn main() {
    App::new()
        .insert_resource(WindowDescriptor {
            title: "Desktop App".to_string(),
            ..Default::default()
        })
        .add_plugin(DesktopPlugin::<NoUiState, NoUiAction, NoAsyncAction>::new(Root))
        .run();
}

fn Root(cx: Scope) -> Element {
    let name = use_state(&cx, || "world".to_string());

    cx.render(rsx! {
        h1 { "Hello, {name} !" }

        input {
            value: "{name}",
            oninput: |e| {
                name.set(e.value.to_string());
            },
        }
    })
}

键盘处理

命令行应用程序

CliPlugin

代码示例
# Cargo.toml

[dependencies]
dip = { version = "0.2", features = ["cli"] }
clap = { version = "3.2", features = ["derive"] }
use dip::{bevy::log::LogPlugin, prelude::*};

fn main() {
    App::new()
        .add_plugin(CliPlugin::<NoAsyncAction>::oneshot())
        .add_plugin(ActionPlugin)
        .add_plugin(LogPlugin)
        .add_system(log_root_arg)
        .add_system(log_path_flag)
        .add_system(handle_hello)
        .add_system(handle_task)
        .add_system(handle_ping)
        .run();
}

#[derive(CliPlugin, clap::Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
    root_arg: Option<String>,

    #[clap(short, long)]
    path: Option<String>,

    #[clap(subcommand)]
    action: Action,
}

#[derive(SubcommandPlugin, clap::Subcommand, Clone)]
pub enum Action {
    // Named variant
    Hello { name: Option<String> },
    // Unnamed
    Hello2(Hello2Args),
    // Unit
    Ping,
}

#[derive(clap::Args, Debug, Clone)]
pub struct Hello2Args {
  name: Option<String>,
}

fn log_root_arg(cli: Res<Cli>) {
    if let Some(arg) = &cli.root_arg {
        info!("root arg: {:?}", arg);
    }
}

fn log_path_flag(cli: Res<Cli>) {
    if let Some(path) = &cli.path {
        info!("path flag: {:?}", path);
    }
}

fn handle_hello(mut events: EventReader<HelloAction>) {
    for e in events.iter() {
        info!("Hello, {}!", e.name.clone().unwrap_or("world".to_string()));
    }
}

fn handle_task(mut events: EventReader<Hello2Action>) {
    for e in events.iter() {
        info!("Hello, {}!", e.name.clone().unwrap_or("world".to_string()));
    }
}

fn handle_ping(mut events: EventReader<PingAction>) {
    for _ in events.iter() {
        info!("Pong !");
    }
}
cargo run -- --help

dip-cli-example 0.1.0
Junichi Sugiura
Example binary project to showcase CliPlugin usage.

USAGE:
    cli [OPTIONS] [ROOT_ARG] <SUBCOMMAND>

ARGS:
    <ROOT_ARG>

OPTIONS:
    -h, --help           Print help information
    -p, --path <PATH>
    -V, --version        Print version information

SUBCOMMANDS:
    hello
    hello2
    help     Print this message or the help of the given subcommand(s)
    ping

状态管理(受 Redux 启发)

UiStatePlugin,UiActionPlugin

代码示例
# Cargo.toml

[dependencies]
dip = { version = "0.2", features = ["desktop"] }

# Removing this crate throws error.
# This is because some derive macros generates code using sub crate name instead of root
# (e.x. bevy_ecs::Component vs bevy::ecs::Compoent)
bevy_ecs = "0.8"
use dip::prelude::*;

fn main() {
    App::new()
        // Step 7. Put it all together
        .add_plugin(DesktopPlugin::<UiState, UiAction, NoAsyncAction>::new(Root))
        .add_plugin(UiStatePlugin) // generated by #[ui_state]
        .add_plugin(UiActionPlugin) // generated by #[ui_action]
        .add_system(update_name)
        .run();
}

// Step 1: Define UiState
// Each field represents root state. You can create multiple of them.
// This macro generates UiState enum and UiStatePlugin which will be used in step 7.
#[ui_state]
struct UiState {
    name: Name,
}

// Make sure to wrap primitive types or common type such as String with named struct or enum.
// You need to distinguish types in order to query specific root state in step 4 (system).
#[derive(Clone, Debug)]
pub struct Name {
    value: String,
}

// This is how you define default value for Name root state.
impl Default for Name {
    fn default() -> Self {
        Self {
            value: "world".to_string(),
        }
    }
}

// Step 2. Define actions
// Create as many as actions with struct or enum.
#[derive(Clone, Debug)]
pub struct UpdateName {
    value: String,
}

// Step 3. Implement action creators
// Each method needs to return one of actions that you defined in step 2.
// This macro derives UiActionPlugin and UiAction which will be used in step 7.
#[ui_action]
impl ActionCreator {
    fn update_name(value: String) -> UpdateName {
        UpdateName { value }
    }
}

// Step 4. Implement systems to handle each action defined in step 2.
// System is like reducer in Redux but more flexible.
fn update_name(mut events: EventReader<UpdateName>, mut name: ResMut<Name>) {
    for action in events.iter() {
        name.value = action.value.clone();
    }
}

fn Root(cx: Scope) -> Element {
    // Step 5. Select state
    let name = use_read(&cx, NAME);

    let window = use_window::<UiAction, NoAsyncAction>(&cx);

    cx.render(rsx! {
        h1 { "Hello, {name.value} !" }

        input {
            value: "{name.value}",
            oninput: |e| {
                // Step 6. Dispatch the action !
                window.send(UiAction::update_name(e.value.to_string()));
            },
        }
    })
}

关于 Bevy 和 Dioxus

Bevy

https://github.com/bevyengine/bevy

  • 基于实体组件系统 (ECS) 设计模式的数据驱动游戏引擎
  • 灵活的插件设计
  • 插件生态系统

Bevy是一个基于Rust的,基于实体组件系统(ECS)设计模式的先进游戏引擎。把它想象成一个类似于Redux的全局状态管理工具,但性能更出色,因为所有系统都会尽可能并行运行。得益于其插件系统,已经有许多第三方Bevy插件。想象一下,将核心逻辑作为CorePlugin实现,将其与UI层分离。您可以从dip::desktop开始,构建桌面应用程序。然后假设您想在将来某个时候发布一个元宇宙版本,这就像将UI插件切换为Bevy的3D渲染插件一样简单,同时仍然使用相同的CorePlugin。

Tauri

Tao是一个窗口管理器,是winit的分支。`DesktopPlugin`依赖于这个库来渲染webview。而webview由WRY提供支持,WRY本质上是一个围绕特定操作系统webview的Rust封装。

为什么不选择Tauri呢?

如果您想用除Rust以外的任何语言编写前端,那么Tauri是一个更好的选择!如果您想全栈使用Rust,那么dip就非常出色。

Dioxus

https://github.com/DioxusLabs/dioxus

  • 跨平台(macOS、Linux、Windows、TUI等)
  • 类似React的声明式UI库
  • 虚拟DOM比React快3倍
  • 最小包大小比Electron轻20倍左右(8 MB vs 160MB)

Dioxus是一个跨平台的声明式UI库。它提供了React开发者期望的熟悉功能,如组件、状态、属性、钩子、全局状态和路由。如果您熟悉任何现代的状态驱动UI框架,您应该能够阅读或编写Dioxus组件而无需了解Rust。

示例

请确保安装Tauri的所有先决条件。

克隆仓库

gh repo clone diptools/dip
cd dip

计数器示例

cargo run --example counter --features desktop

更多信息请参阅examples/目录。

TodoMVC示例

  1. 安装dip CLI。
cargo install dip

# or install local binary
cargo install --path .
  1. 编译Tailwind CSS
dip build -p examples/todomvc
  1. 运行
cargo run -p todomvc

依赖项

~30–79MB
~1.5M SLoC