├── .gitignore ├── .cargo └── config ├── phira-mp-server ├── locales │ ├── zh-CN.ftl │ ├── zh-TW.ftl │ └── en-US.ftl ├── Cargo.toml └── src │ ├── main.rs │ ├── server.rs │ ├── l10n.rs │ ├── room.rs │ └── session.rs ├── phira-mp-macros ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── phira-mp-client ├── Cargo.toml └── src │ └── lib.rs ├── phira-mp-common ├── Cargo.toml └── src │ ├── lib.rs │ ├── command.rs │ └── bin.rs ├── README.zh-CN.md ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /log 2 | /target 3 | /server_config.yml -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "tokio_unstable"] 3 | -------------------------------------------------------------------------------- /phira-mp-server/locales/zh-CN.ftl: -------------------------------------------------------------------------------- 1 | 2 | create-id-occupied = 房间 ID 已被占用 3 | 4 | join-game-ongoing = 游戏正在进行中 5 | join-room-full = 房间已满 6 | join-room-locked = 房间已锁定 7 | join-cant-monitor = 权限不足,不能旁观房间 8 | 9 | start-no-chart-selected = 还没有选择谱面 10 | -------------------------------------------------------------------------------- /phira-mp-server/locales/zh-TW.ftl: -------------------------------------------------------------------------------- 1 | 2 | create-id-occupied = 房間 ID 已被佔用 3 | 4 | join-game-ongoing = 遊戲正在進行中 5 | join-room-full = 房間已滿 6 | join-room-locked = 房間已鎖定 7 | join-cant-monitor = 權限不足,不能旁觀房間 8 | 9 | start-no-chart-selected = 還沒有選擇譜面 10 | -------------------------------------------------------------------------------- /phira-mp-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phira-mp-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | proc-macro2 = "1" 11 | syn = { version = "1.0", features = ["full"] } 12 | quote = "1.0" 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "phira-mp-common", 4 | "phira-mp-client", 5 | "phira-mp-macros", 6 | "phira-mp-server", 7 | ] 8 | resolver = "2" 9 | 10 | [profile.release] 11 | opt-level = 2 12 | debug = 1 13 | 14 | [profile.dev.package."*"] 15 | opt-level = 2 16 | -------------------------------------------------------------------------------- /phira-mp-server/locales/en-US.ftl: -------------------------------------------------------------------------------- 1 | 2 | create-id-occupied = Room ID is occupied 3 | 4 | join-game-ongoing = Game is ongoing 5 | join-room-full = Room is full 6 | join-room-locked = Room is locked 7 | join-cant-monitor = Permission denied. You can't monitor this room. 8 | 9 | start-no-chart-selected = No chart selected 10 | -------------------------------------------------------------------------------- /phira-mp-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phira-mp-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | chrono = "0.4.26" 11 | dashmap = "5.4.0" 12 | phira-mp-common = { path = "../phira-mp-common" } 13 | tokio = "*" 14 | tracing = "0.1.37" 15 | uuid = { version = "1.3.3", features = ["v4"] } 16 | -------------------------------------------------------------------------------- /phira-mp-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phira-mp-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0", features = ["backtrace"] } 8 | byteorder = "1.4.3" 9 | half = "~2.2.1" 10 | tap = "1.0.1" 11 | tokio = { version = "1.27.0", features = ["macros", "rt-multi-thread", "rt", "net", "io-util", "time", "sync"] } 12 | tracing = "0.1.37" 13 | 14 | phira-mp-macros = { path = "../phira-mp-macros" } 15 | uuid = { version = "1.3.3", features = ["v4"] } 16 | chrono = "0.4.26" 17 | -------------------------------------------------------------------------------- /phira-mp-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phira-mp-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0", features = ["backtrace"] } 8 | phira-mp-common = { path = "../phira-mp-common" } 9 | reqwest = { version = "0.11.18", features = ["json"] } 10 | serde = { version = "1.0.163", features = ["derive"] } 11 | serde_yaml = "0.9" 12 | tap = "1.0.1" 13 | tokio = "*" 14 | tracing = "0.1.37" 15 | uuid = { version = "1.3.3", features = ["v4"] } 16 | 17 | fluent = "0.16.0" 18 | fluent-syntax = "0.11.0" 19 | intl-memoizer = "0.5.1" 20 | lru = "0.10.0" 21 | once_cell = "1.18.0" 22 | rand = "0.8.5" 23 | tracing-appender = "0.2.2" 24 | tracing-log = "0.1.3" 25 | tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } 26 | unic-langid = { version = "0.9.1", features = ["macros"] } 27 | clap = { version = "4.4.11", features = ["derive"] } 28 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # phira-mp 2 | 3 | `phira-mp` 是一个用 Rust 开发的项目。 以下是部署和运行该项目服务端的步骤。 4 | 5 | 简体中文 | [English Version](README.md) 6 | ## 环境 7 | 8 | - Rust 1.70 或更高版本 9 | 10 | ## 服务端安装 11 | ### 对于 `Linux` 用户 12 | #### 依赖 13 | 首先,如果尚未安装 Rust,请安装。 您可以按照 https://www.rust-lang.org/tools/install 中的说明进行操作 14 | 15 | 对于 Ubuntu 或 Debian 用户,如果尚未安装“curl”,请使用以下命令进行安装: 16 | 17 | ```shell 18 | sudo apt install curl 19 | ``` 20 | 对于 Fedora 或 CentOS 用户,请使用以下命令: 21 | ```shell 22 | sudo yum install curl 23 | ``` 24 | 安装curl后,使用以下命令安装Rust: 25 | ```shell 26 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 27 | ``` 28 | 然后,构建项目: 29 | ```shell 30 | cargo build --release -p phira-mp-server 31 | ``` 32 | #### 运行服务端 33 | 您可以使用以下命令运行该应用程序: 34 | ```shell 35 | RUST_LOG=info target/release/phira-mp-server 36 | ``` 37 | 38 | 也可以通过参数指定端口: 39 | ```shell 40 | RUST_LOG=info target/release/phira-mp-server --port 8080 41 | ``` 42 | 43 | ### For docker 44 | 45 | 1. 创建 Dockerfile 46 | ``` 47 | FROM ubuntu:22.04 48 | 49 | RUN apt-get update && apt-get -y upgrade && apt-get install -y curl git build-essential pkg-config openssl libssl-dev 50 | 51 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 52 | ENV PATH="/root/.cargo/bin:${PATH}" 53 | WORKDIR /root/ 54 | RUN git clone https://github.com/TeamFlos/phira-mp 55 | WORKDIR /root/phira-mp 56 | RUN cargo build --release -p phira-mp-server 57 | 58 | ENTRYPOINT ["/root/phira-mp/target/release/phira-mp-server", "--port", ""] 59 | ``` 60 | 61 | 2. 构建镜像 62 | `docker build --tag phira-mp .` 63 | 64 | 3. 运行容器 65 | `docker run -it --name phira-mp -p : --restart=unless-stopped phira-mp` 66 | 67 | #### 故障排除 68 | 如果遇到与 openssl 相关的问题,请确保安装了 libssl-dev(适用于 Ubuntu 或 Debian)或 openssl-devel(适用于 Fedora 或 CentOS)。 如果问题仍然存在,您可以为编译过程设置 OPENSSL_DIR 环境变量。 69 | 70 | 如果您在 Linux 上进行编译并以 Linux 为目标,并收到有关缺少 pkg-config 的消息,则可能需要安装它: 71 | 72 | ```shell 73 | # 对于 Ubuntu 或 Debian 74 | sudo apt install pkg-config libssl-dev 75 | 76 | # 对于 Fedora 或 CentOS 77 | sudo dnf install pkg-config openssl-devel 78 | ``` 79 | 其他问题请参考具体错误信息并相应调整您的环境。 80 | 81 | #### 监控 82 | 您可以检查正在运行的进程及其正在侦听的端口: 83 | ```shell 84 | ps -aux | grep phira-mp-server 85 | netstat -tuln | grep 12345 86 | ``` 87 | ![image](https://github.com/okatu-loli/phira-mp/assets/53247097/b533aee7-03c2-4920-aae9-a0b9e70ed576) 88 | 89 | ## 对于 Windows 或 Android 用户 90 | 查看: [https://docs.qq.com/doc/DU1dlekx3U096REdD](https://docs.qq.com/doc/DU1dlekx3U096REdD) 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phira-mp 2 | 3 | `phira-mp` is a project developed with Rust. Below are the steps to deploy and run this project. 4 | 5 | [简体中文](README.zh-CN.md) | English Version 6 | 7 | ## Environment 8 | 9 | - Rust 1.70 or later 10 | 11 | ## Server Installation 12 | 13 | ### For Linux 14 | 15 | #### Dependent 16 | First, install Rust if you haven't already. You can do so by following the instructions at https://www.rust-lang.org/tools/install 17 | 18 | For Ubuntu or Debian users, use the following command to install `curl` if it isn't installed yet: 19 | 20 | ```shell 21 | sudo apt install curl 22 | ``` 23 | For Fedora or CentOS users, use the following command: 24 | ```shell 25 | sudo yum install curl 26 | ``` 27 | After curl is installed, install Rust with the following command: 28 | ```shell 29 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 30 | ``` 31 | Then, build the project: 32 | ```shell 33 | cargo build --release -p phira-mp-server 34 | ``` 35 | #### Running the Server 36 | You can run the application with the following command: 37 | ```shell 38 | RUST_LOG=info target/release/phira-mp-server 39 | ``` 40 | 41 | The port can also be specified via parameters: 42 | ```shell 43 | RUST_LOG=info target/release/phira-mp-server --port 8080 44 | ``` 45 | 46 | ### For docker 47 | 48 | 1. Create Dockerfile 49 | ``` 50 | FROM ubuntu:22.04 51 | 52 | RUN apt-get update && apt-get -y upgrade && apt-get install -y curl git build-essential pkg-config openssl libssl-dev 53 | 54 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 55 | ENV PATH="/root/.cargo/bin:${PATH}" 56 | WORKDIR /root/ 57 | RUN git clone https://github.com/TeamFlos/phira-mp 58 | WORKDIR /root/phira-mp 59 | RUN cargo build --release -p phira-mp-server 60 | 61 | ENTRYPOINT ["/root/phira-mp/target/release/phira-mp-server", "--port", ""] 62 | ``` 63 | 64 | 2. Build the image 65 | `docker build --tag phira-mp .` 66 | 67 | 3. Run the container 68 | `docker run -it --name phira-mp -p : --restart=unless-stopped phira-mp` 69 | 70 | #### Troubleshooting 71 | If you encounter issues related to openssl, ensure that you have libssl-dev (for Ubuntu or Debian) or openssl-devel (for Fedora or CentOS) installed. If the issue persists, you can set the OPENSSL_DIR environment variable for the compilation process. 72 | 73 | If you're compiling on Linux and targeting Linux and get a message about pkg-config being missing, you may need to install it: 74 | 75 | ```shell 76 | # For Ubuntu or Debian 77 | sudo apt install pkg-config libssl-dev 78 | 79 | # For Fedora or CentOS 80 | sudo dnf install pkg-config openssl-devel 81 | ``` 82 | For other issues, please refer to the specific error messages and adjust your environment accordingly. 83 | 84 | #### Monitoring 85 | You can check the running process and the port it's listening on with: 86 | ```shell 87 | ps -aux | grep phira-mp-server 88 | netstat -tuln | grep 12345 89 | ``` 90 | ![image](https://github.com/okatu-loli/phira-mp/assets/53247097/b533aee7-03c2-4920-aae9-a0b9e70ed576) 91 | 92 | ## For Windows or Android 93 | View: [https://docs.qq.com/doc/DU1dlekx3U096REdD](https://docs.qq.com/doc/DU1dlekx3U096REdD) 94 | 95 | -------------------------------------------------------------------------------- /phira-mp-server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod l10n; 2 | 3 | mod room; 4 | pub use room::*; 5 | 6 | mod server; 7 | pub use server::*; 8 | 9 | mod session; 10 | pub use session::*; 11 | 12 | use anyhow::Result; 13 | use clap::Parser; 14 | use std::{ 15 | collections::{ 16 | hash_map::{Entry, VacantEntry}, 17 | HashMap, 18 | }, 19 | net::{Ipv6Addr, SocketAddr}, 20 | path::Path, 21 | }; 22 | use tokio::{net::TcpListener, sync::RwLock}; 23 | use tracing::warn; 24 | use tracing_appender::non_blocking::WorkerGuard; 25 | use uuid::Uuid; 26 | 27 | pub type SafeMap = RwLock>; 28 | pub type IdMap = SafeMap; 29 | 30 | fn vacant_entry(map: &mut HashMap) -> VacantEntry<'_, Uuid, V> { 31 | let mut id = Uuid::new_v4(); 32 | while map.contains_key(&id) { 33 | // 修正此处的语法错误 34 | id = Uuid::new_v4(); 35 | } 36 | match map.entry(id) { 37 | Entry::Vacant(entry) => entry, 38 | _ => unreachable!(), 39 | } 40 | } 41 | 42 | pub fn init_log(file: &str) -> Result { 43 | use tracing::{metadata::LevelFilter, Level}; 44 | use tracing_log::LogTracer; 45 | use tracing_subscriber::{filter, fmt, prelude::*, EnvFilter}; 46 | 47 | let log_dir = Path::new("log"); 48 | if log_dir.exists() { 49 | if !log_dir.is_dir() { 50 | panic!("log exists and is not a folder"); 51 | } 52 | } else { 53 | std::fs::create_dir(log_dir).expect("failed to create log folder"); 54 | } 55 | 56 | LogTracer::init()?; 57 | 58 | let (non_blocking, guard) = 59 | tracing_appender::non_blocking(tracing_appender::rolling::hourly(log_dir, file)); 60 | 61 | let subscriber = tracing_subscriber::registry() 62 | .with( 63 | fmt::layer() 64 | .with_writer(non_blocking) 65 | .with_filter(LevelFilter::DEBUG), 66 | ) 67 | .with( 68 | fmt::layer() 69 | .with_writer(std::io::stdout) 70 | .with_filter(EnvFilter::from_default_env()), 71 | ) 72 | .with( 73 | filter::Targets::new() 74 | .with_target("hyper", Level::INFO) 75 | .with_target("rustls", Level::INFO) 76 | .with_target("isahc", Level::INFO) 77 | .with_default(Level::TRACE), 78 | ); 79 | 80 | tracing::subscriber::set_global_default(subscriber).expect("unable to set global subscriber"); 81 | Ok(guard) 82 | } 83 | 84 | /// Command line arguments 85 | #[derive(Parser, Debug)] 86 | #[clap(author, version, about, long_about = None)] 87 | struct Args { 88 | #[clap( 89 | short, 90 | long, 91 | default_value_t = 12346, 92 | help = "Specify the port number to use for the server" 93 | )] 94 | port: u16, 95 | } 96 | 97 | #[tokio::main] 98 | async fn main() -> Result<()> { 99 | let _guard = init_log("phira-mp")?; 100 | 101 | let args = Args::parse(); 102 | let port = args.port; 103 | let addrs: &[SocketAddr] = &[ 104 | SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port), 105 | ]; 106 | 107 | // 打印本地地址和端口 108 | for addr in addrs { 109 | println!("Local Address: {}", addr); 110 | } 111 | 112 | let listener: Server = TcpListener::bind(addrs).await?.into(); 113 | 114 | loop { 115 | if let Err(err) = listener.accept().await { 116 | warn!("failed to accept: {err:?}"); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /phira-mp-server/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::{vacant_entry, IdMap, Room, SafeMap, Session, User}; 2 | use anyhow::Result; 3 | use phira_mp_common::RoomId; 4 | use serde::Deserialize; 5 | use std::{fs::File, sync::Arc}; 6 | use tokio::{net::TcpListener, sync::mpsc, task::JoinHandle}; 7 | use tracing::{info, warn}; 8 | use uuid::Uuid; 9 | 10 | #[derive(Debug, Deserialize)] 11 | pub struct Chart { 12 | pub id: i32, 13 | pub name: String, 14 | } 15 | 16 | #[derive(Debug, Deserialize)] 17 | pub struct ServerConfig { 18 | pub monitors: Vec, 19 | } 20 | impl Default for ServerConfig { 21 | fn default() -> Self { 22 | Self { monitors: vec![2] } 23 | } 24 | } 25 | 26 | #[derive(Debug, Deserialize)] 27 | pub struct Record { 28 | pub id: i32, 29 | pub player: i32, 30 | pub score: i32, 31 | pub perfect: i32, 32 | pub good: i32, 33 | pub bad: i32, 34 | pub miss: i32, 35 | pub max_combo: i32, 36 | pub accuracy: f32, 37 | pub full_combo: bool, 38 | pub std: f32, 39 | pub std_score: f32, 40 | } 41 | 42 | pub struct ServerState { 43 | pub config: ServerConfig, 44 | pub sessions: IdMap>, 45 | pub users: SafeMap>, 46 | 47 | pub rooms: SafeMap>, 48 | 49 | pub lost_con_tx: mpsc::Sender, 50 | } 51 | 52 | pub struct Server { 53 | state: Arc, 54 | listener: TcpListener, 55 | lost_con_handle: JoinHandle<()>, 56 | } 57 | 58 | impl From for Server { 59 | fn from(listener: TcpListener) -> Self { 60 | let (lost_con_tx, mut lost_con_rx) = mpsc::channel(16); 61 | let config: ServerConfig = File::open("server_config.yml") 62 | .ok() 63 | .and_then(|f| serde_yaml::from_reader(f).ok()) 64 | .unwrap_or_default(); 65 | let state = Arc::new(ServerState { 66 | config, 67 | sessions: IdMap::default(), 68 | users: SafeMap::default(), 69 | 70 | rooms: SafeMap::default(), 71 | 72 | lost_con_tx, 73 | }); 74 | let lost_con_handle = tokio::spawn({ 75 | let state = Arc::clone(&state); 76 | async move { 77 | while let Some(id) = lost_con_rx.recv().await { 78 | warn!("lost connection with {id}"); 79 | if let Some(session) = state.sessions.write().await.remove(&id) { 80 | if session 81 | .user 82 | .session 83 | .read() 84 | .await 85 | .as_ref() 86 | .map_or(false, |it| it.ptr_eq(&Arc::downgrade(&session))) 87 | { 88 | Arc::clone(&session.user).dangle().await; 89 | } 90 | } 91 | } 92 | } 93 | }); 94 | 95 | Self { 96 | listener, 97 | state, 98 | 99 | lost_con_handle, 100 | } 101 | } 102 | } 103 | 104 | impl Server { 105 | pub async fn accept(&self) -> Result<()> { 106 | let (stream, addr) = self.listener.accept().await?; 107 | let mut guard = self.state.sessions.write().await; 108 | let entry = vacant_entry(&mut guard); 109 | let session = Session::new(*entry.key(), stream, Arc::clone(&self.state)).await?; 110 | info!( 111 | "received connections from {addr} ({}), version: {}", 112 | session.id, 113 | session.version() 114 | ); 115 | entry.insert(session); 116 | Ok(()) 117 | } 118 | } 119 | 120 | impl Drop for Server { 121 | fn drop(&mut self) { 122 | self.lost_con_handle.abort(); 123 | } 124 | } -------------------------------------------------------------------------------- /phira-mp-server/src/l10n.rs: -------------------------------------------------------------------------------- 1 | use fluent::{bundle::FluentBundle, FluentArgs, FluentError, FluentResource}; 2 | use fluent_syntax::ast::Pattern; 3 | use lru::LruCache; 4 | use once_cell::sync::Lazy; 5 | use std::{borrow::Cow, cell::RefCell, collections::HashMap, sync::Arc}; 6 | use tracing::error; 7 | use unic_langid::{langid, LanguageIdentifier}; 8 | 9 | pub static LANGS: [&str; 3] = ["en-US", "zh-CN", "zh-TW"]; // this should be consistent with the macro below (BUNDLES) 10 | pub static IDENTS: Lazy<[LanguageIdentifier; 3]> = 11 | Lazy::new(|| LANGS.map(|it| it.parse().unwrap())); 12 | 13 | pub struct L10nBundles { 14 | inner: Vec>, 15 | map: HashMap, 16 | } 17 | 18 | static BUNDLES: Lazy = Lazy::new(|| { 19 | let mut map = HashMap::new(); 20 | macro_rules! bundle { 21 | ($locale:literal) => {{ 22 | map.insert(langid!($locale), map.len()); 23 | let mut bundle = FluentBundle::new_concurrent(vec![langid!($locale)]); 24 | bundle 25 | .add_resource( 26 | FluentResource::try_new( 27 | include_str!(concat!( 28 | env!("CARGO_MANIFEST_DIR"), 29 | "/locales/", 30 | $locale, 31 | ".ftl" 32 | )) 33 | .to_owned(), 34 | ) 35 | .unwrap(), 36 | ) 37 | .unwrap(); 38 | bundle.set_use_isolating(false); 39 | bundle 40 | }}; 41 | } 42 | L10nBundles { 43 | inner: vec![bundle!("en-US"), bundle!("zh-CN"), bundle!("zh-TW")], 44 | map, 45 | } 46 | }); 47 | 48 | pub struct L10nLocal { 49 | cache: [LruCache<&'static str, (usize, &'static Pattern<&'static str>)>; 3], 50 | } 51 | 52 | impl L10nLocal { 53 | fn new() -> Self { 54 | let size = 64.try_into().unwrap(); 55 | Self { 56 | cache: std::array::from_fn(|_| LruCache::new(size)), 57 | } 58 | } 59 | 60 | fn format_with_errors<'s>( 61 | &mut self, 62 | lang: LanguageIdentifier, 63 | key: &'static str, 64 | args: Option<&'s FluentArgs<'s>>, 65 | errors: &mut Vec, 66 | ) -> Cow<'s, str> { 67 | let id = *BUNDLES.map.get(&lang).unwrap(); 68 | let (id, pattern) = self.cache[id].get_or_insert(key, || { 69 | if let Some((id, message)) = BUNDLES.inner[id].get_message(key).map(|msg| (id, msg)) { 70 | return (id, message.value().unwrap()); 71 | } 72 | panic!("no translation found for {key} (lang={lang})"); 73 | }); 74 | BUNDLES.inner[*id].format_pattern(pattern, args, errors) 75 | } 76 | 77 | pub fn format<'s>( 78 | &mut self, 79 | lang: LanguageIdentifier, 80 | key: &'static str, 81 | args: Option<&'s FluentArgs<'s>>, 82 | ) -> Cow<'s, str> { 83 | let mut errors = Vec::new(); 84 | let res = self.format_with_errors(lang, key, args, &mut errors); 85 | for error in errors { 86 | error!("message error {}: {:?}", key, error); 87 | } 88 | res 89 | } 90 | } 91 | 92 | thread_local! { 93 | static L10N_LOCAL: RefCell = RefCell::new(L10nLocal::new()); 94 | } 95 | 96 | #[derive(Clone)] 97 | pub struct Language(pub LanguageIdentifier); 98 | 99 | impl Default for Language { 100 | fn default() -> Self { 101 | Self(IDENTS[0].clone()) 102 | } 103 | } 104 | 105 | impl Language { 106 | pub fn format<'s>(&self, key: &'static str, args: Option<&'s FluentArgs<'s>>) -> Cow<'s, str> { 107 | L10N_LOCAL.with(|it| it.borrow_mut().format(self.0.clone(), key, args)) 108 | } 109 | } 110 | 111 | tokio::task_local! { 112 | pub static LANGUAGE: Arc; 113 | } 114 | 115 | #[macro_export] 116 | macro_rules! tl { 117 | ($key:literal) => { 118 | $crate::l10n::LANGUAGE.with(|l| l.format($key, None)) 119 | }; 120 | ($key:literal, $($attr:expr => $value:expr), *) => { 121 | $crate::l10n::LANGUAGE.with(|l| l.format($key, Some(&fluent::fluent_args![$($attr => $value), *])).into_owned()) 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /phira-mp-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bin; 2 | pub use bin::*; 3 | 4 | mod command; 5 | pub use command::*; 6 | 7 | use anyhow::{bail, Error, Result}; 8 | use std::{future::Future, marker::PhantomData, sync::Arc, time::Duration}; 9 | use tokio::{ 10 | io::{AsyncReadExt, AsyncWriteExt}, 11 | net::TcpStream, 12 | sync::mpsc, 13 | task::JoinHandle, 14 | }; 15 | use tracing::{error, trace, warn}; 16 | 17 | pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(3); 18 | pub const HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(2); 19 | pub const HEARTBEAT_DISCONNECT_TIMEOUT: Duration = Duration::from_secs(10); 20 | 21 | pub fn encode_packet(payload: &impl BinaryData, vec: &mut Vec) { 22 | BinaryWriter::new(vec).write(payload).unwrap(); 23 | } 24 | 25 | pub fn decode_packet(data: &[u8]) -> Result 26 | where 27 | T: BinaryData, 28 | { 29 | BinaryReader::new(data).read() 30 | } 31 | 32 | pub struct Stream { 33 | version: u8, 34 | 35 | send_tx: Arc>, 36 | 37 | send_task_handle: JoinHandle<()>, 38 | recv_task_handle: JoinHandle>, 39 | 40 | _marker: PhantomData<(S, R)>, 41 | } 42 | 43 | impl Stream 44 | where 45 | S: BinaryData + std::fmt::Debug + Send + Sync + 'static, 46 | R: BinaryData + std::fmt::Debug + Send + 'static, 47 | { 48 | pub async fn new( 49 | version: Option, 50 | stream: TcpStream, 51 | mut handler: Box>, R) -> F + Send + Sync>, 52 | ) -> Result 53 | where 54 | F: Future + Send + 'static, 55 | { 56 | stream.set_nodelay(true)?; 57 | let (mut read, mut write) = stream.into_split(); 58 | let version = if let Some(version) = version { 59 | write.write_u8(version).await?; 60 | version 61 | } else { 62 | read.read_u8().await? 63 | }; 64 | 65 | let (send_tx, mut send_rx) = mpsc::channel(1024); 66 | let send_tx = Arc::new(send_tx); 67 | let send_task_handle = tokio::spawn({ 68 | async move { 69 | let mut buffer = Vec::new(); 70 | let mut len_buf = [0u8; 5]; 71 | while let Some(payload) = send_rx.recv().await { 72 | buffer.clear(); 73 | encode_packet(&payload, &mut buffer); 74 | trace!("sending {} bytes ({payload:?}): {buffer:?}", buffer.len()); 75 | 76 | let mut x = buffer.len() as u32; 77 | let mut n = 0; 78 | loop { 79 | len_buf[n] = (x & 0x7f) as u8; 80 | n += 1; 81 | x >>= 7; 82 | if x == 0 { 83 | break; 84 | } else { 85 | len_buf[n - 1] |= 0x80; 86 | } 87 | } 88 | 89 | if let Err(err) = async { 90 | write.write_all(&len_buf[..n]).await?; 91 | write.write_all(&buffer).await?; 92 | Ok::<_, Error>(()) 93 | } 94 | .await 95 | { 96 | error!("failed to send: {err:?}"); 97 | } 98 | } 99 | } 100 | }); 101 | 102 | let recv_task_handle = tokio::spawn({ 103 | let send_tx = Arc::clone(&send_tx); 104 | #[allow(clippy::read_zero_byte_vec)] 105 | async move { 106 | let mut buffer = Vec::new(); 107 | loop { 108 | let mut len = 0u32; 109 | let mut pos = 0; 110 | loop { 111 | let byte = read.read_u8().await?; 112 | len |= ((byte & 0x7f) as u32) << pos; 113 | pos += 7; 114 | if byte & 0x80 == 0 { 115 | break; 116 | } 117 | if pos > 32 { 118 | bail!("invalid length"); 119 | } 120 | } 121 | if len > 2 * 1024 * 1024 { 122 | bail!("data packet too large"); 123 | } 124 | let len = len as usize; 125 | 126 | buffer.resize(len, 0); 127 | read.read_exact(&mut buffer).await?; 128 | trace!("received {} bytes: {buffer:?}", buffer.len()); 129 | 130 | let payload: R = match decode_packet(&buffer) { 131 | Ok(val) => val, 132 | Err(err) => { 133 | warn!("invalid packet: {err:?} {buffer:?}"); 134 | break; 135 | } 136 | }; 137 | trace!("decodes to {payload:?}"); 138 | handler(Arc::clone(&send_tx), payload).await; 139 | } 140 | Ok(()) 141 | } 142 | }); 143 | 144 | Ok(Self { 145 | version, 146 | 147 | send_tx, 148 | 149 | send_task_handle, 150 | recv_task_handle, 151 | 152 | _marker: PhantomData::default(), 153 | }) 154 | } 155 | 156 | pub fn version(&self) -> u8 { 157 | self.version 158 | } 159 | 160 | pub async fn send(&self, payload: S) -> Result<()> { 161 | self.send_tx.send(payload).await?; 162 | Ok(()) 163 | } 164 | 165 | pub fn blocking_send(&self, payload: S) -> Result<()> { 166 | self.send_tx.blocking_send(payload)?; 167 | Ok(()) 168 | } 169 | } 170 | 171 | impl Drop for Stream { 172 | fn drop(&mut self) { 173 | self.send_task_handle.abort(); 174 | self.recv_task_handle.abort(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /phira-mp-common/src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::{BinaryData, BinaryReader, BinaryWriter}; 2 | use anyhow::{bail, Result}; 3 | use half::f16; 4 | use phira_mp_macros::BinaryData; 5 | use std::{collections::HashMap, fmt::Display, sync::Arc}; 6 | 7 | type SResult = Result; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct CompactPos { 11 | pub(crate) x: f16, 12 | pub(crate) y: f16, 13 | } 14 | 15 | impl BinaryData for CompactPos { 16 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 17 | Ok(Self { 18 | x: f16::from_bits(r.read()?), 19 | y: f16::from_bits(r.read()?), 20 | }) 21 | } 22 | 23 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 24 | w.write_val(self.x.to_bits())?; 25 | w.write_val(self.y.to_bits())?; 26 | Ok(()) 27 | } 28 | } 29 | 30 | impl CompactPos { 31 | pub fn new(x: f32, y: f32) -> Self { 32 | Self { 33 | x: f16::from_f32(x), 34 | y: f16::from_f32(y), 35 | } 36 | } 37 | 38 | pub fn x(&self) -> f32 { 39 | self.x.to_f32() 40 | } 41 | 42 | pub fn y(&self) -> f32 { 43 | self.y.to_f32() 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 48 | pub struct Varchar(String); 49 | impl Display for Varchar { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | self.0.fmt(f) 52 | } 53 | } 54 | impl TryFrom for Varchar { 55 | type Error = anyhow::Error; 56 | 57 | fn try_from(value: String) -> Result { 58 | if value.len() > N { 59 | bail!("string too long"); 60 | } 61 | Ok(Self(value)) 62 | } 63 | } 64 | impl BinaryData for Varchar { 65 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 66 | let len = r.uleb()? as usize; 67 | if len > N { 68 | bail!("string too long"); 69 | } 70 | Ok(Varchar(String::from_utf8_lossy(r.take(len)?).into_owned())) 71 | } 72 | 73 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 74 | w.write(&self.0) 75 | } 76 | } 77 | 78 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 79 | pub struct RoomId(Varchar<20>); 80 | impl RoomId { 81 | fn validate(self) -> Result { 82 | if self.0 .0.is_empty() 83 | || !self 84 | .0 85 | .0 86 | .chars() 87 | .all(|it| it == '-' || it == '_' || it.is_ascii_alphanumeric()) 88 | { 89 | bail!("invalid room id"); 90 | } 91 | Ok(self) 92 | } 93 | } 94 | 95 | impl From for String { 96 | fn from(value: RoomId) -> Self { 97 | value.0 .0 98 | } 99 | } 100 | 101 | impl TryFrom for RoomId { 102 | type Error = anyhow::Error; 103 | 104 | fn try_from(value: String) -> Result { 105 | Self(value.try_into()?).validate() 106 | } 107 | } 108 | 109 | impl Display for RoomId { 110 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 111 | self.0 .0.fmt(f) 112 | } 113 | } 114 | 115 | impl BinaryData for RoomId { 116 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 117 | Self(Varchar::read_binary(r)?).validate() 118 | } 119 | 120 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 121 | self.0.write_binary(w) 122 | } 123 | } 124 | 125 | impl Varchar { 126 | pub fn into_inner(self) -> String { 127 | self.0 128 | } 129 | } 130 | 131 | #[derive(Debug, Clone, BinaryData)] 132 | pub struct TouchFrame { 133 | pub time: f32, 134 | pub points: Vec<(i8, CompactPos)>, 135 | } 136 | 137 | #[repr(u8)] 138 | #[derive(Debug, Clone, Copy, BinaryData)] 139 | pub enum Judgement { 140 | Perfect, 141 | Good, 142 | Bad, 143 | Miss, 144 | HoldPerfect, 145 | HoldGood, 146 | } 147 | 148 | #[derive(Debug, Clone, BinaryData)] 149 | pub struct JudgeEvent { 150 | pub time: f32, 151 | pub line_id: u32, 152 | pub note_id: u32, 153 | pub judgement: Judgement, 154 | } 155 | 156 | #[derive(Debug, BinaryData)] 157 | pub enum ClientCommand { 158 | Ping, 159 | 160 | Authenticate { token: Varchar<32> }, 161 | Chat { message: Varchar<200> }, 162 | 163 | Touches { frames: Arc> }, 164 | Judges { judges: Arc> }, 165 | 166 | CreateRoom { id: RoomId }, 167 | JoinRoom { id: RoomId, monitor: bool }, 168 | LeaveRoom, 169 | LockRoom { lock: bool }, 170 | CycleRoom { cycle: bool }, 171 | 172 | SelectChart { id: i32 }, 173 | RequestStart, 174 | Ready, 175 | CancelReady, 176 | Played { id: i32 }, 177 | Abort, 178 | } 179 | 180 | #[derive(Clone, Debug, BinaryData)] 181 | pub enum Message { 182 | Chat { 183 | user: i32, 184 | content: String, 185 | }, 186 | CreateRoom { 187 | user: i32, 188 | }, 189 | JoinRoom { 190 | user: i32, 191 | name: String, 192 | }, 193 | LeaveRoom { 194 | user: i32, 195 | name: String, 196 | }, 197 | NewHost { 198 | user: i32, 199 | }, 200 | SelectChart { 201 | user: i32, 202 | name: String, 203 | id: i32, 204 | }, 205 | GameStart { 206 | user: i32, 207 | }, 208 | Ready { 209 | user: i32, 210 | }, 211 | CancelReady { 212 | user: i32, 213 | }, 214 | CancelGame { 215 | user: i32, 216 | }, 217 | StartPlaying, 218 | Played { 219 | user: i32, 220 | score: i32, 221 | accuracy: f32, 222 | full_combo: bool, 223 | }, 224 | GameEnd, 225 | Abort { 226 | user: i32, 227 | }, 228 | LockRoom { 229 | lock: bool, 230 | }, 231 | CycleRoom { 232 | cycle: bool, 233 | }, 234 | } 235 | 236 | #[derive(Debug, BinaryData, Clone, Copy)] 237 | pub enum RoomState { 238 | SelectChart(Option), 239 | WaitingForReady, 240 | Playing, 241 | } 242 | 243 | impl Default for RoomState { 244 | fn default() -> Self { 245 | Self::SelectChart(None) 246 | } 247 | } 248 | 249 | #[derive(Clone, Debug, BinaryData)] 250 | pub struct UserInfo { 251 | pub id: i32, 252 | pub name: String, 253 | pub monitor: bool, 254 | } 255 | 256 | #[derive(Debug, BinaryData, Clone)] 257 | pub struct ClientRoomState { 258 | pub id: RoomId, 259 | pub state: RoomState, 260 | pub live: bool, 261 | pub locked: bool, 262 | pub cycle: bool, 263 | pub is_host: bool, 264 | pub is_ready: bool, 265 | pub users: HashMap, 266 | } 267 | 268 | #[derive(Debug, BinaryData, Clone)] 269 | pub struct JoinRoomResponse { 270 | pub state: RoomState, 271 | pub users: Vec, 272 | pub live: bool, 273 | } 274 | 275 | #[derive(Clone, Debug, BinaryData)] 276 | pub enum ServerCommand { 277 | Pong, 278 | 279 | Authenticate(SResult<(UserInfo, Option)>), 280 | Chat(SResult<()>), 281 | 282 | Touches { 283 | player: i32, 284 | frames: Arc>, 285 | }, 286 | Judges { 287 | player: i32, 288 | judges: Arc>, 289 | }, 290 | 291 | Message(Message), 292 | ChangeState(RoomState), 293 | ChangeHost(bool), 294 | 295 | CreateRoom(SResult<()>), 296 | JoinRoom(SResult), 297 | OnJoinRoom(UserInfo), 298 | LeaveRoom(SResult<()>), 299 | LockRoom(SResult<()>), 300 | CycleRoom(SResult<()>), 301 | 302 | SelectChart(SResult<()>), 303 | RequestStart(SResult<()>), 304 | Ready(SResult<()>), 305 | CancelReady(SResult<()>), 306 | Played(SResult<()>), 307 | Abort(SResult<()>), 308 | } 309 | -------------------------------------------------------------------------------- /phira-mp-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream}; 2 | use quote::quote; 3 | use syn::{ 4 | parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Fields, GenericArgument, 5 | PathArguments, Type, Variant, 6 | }; 7 | 8 | #[proc_macro_derive(BinaryData)] 9 | pub fn derive_model_ex(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 10 | let input = parse_macro_input!(input as DeriveInput); 11 | let res = build_derive(input.ident, input.data); 12 | quote! { 13 | #res 14 | } 15 | .into() 16 | } 17 | 18 | struct TypeInfo { 19 | is_arc: bool, 20 | is_vec: bool, 21 | } 22 | 23 | fn parse_type(typ: &Type) -> TypeInfo { 24 | let (typ, is_arc) = match typ { 25 | Type::Path(path) => { 26 | let last = path.path.segments.last().unwrap(); 27 | if last.ident == "Arc" { 28 | ( 29 | match &last.arguments { 30 | PathArguments::AngleBracketed(arg) => match &arg.args[0] { 31 | GenericArgument::Type(typ) => typ, 32 | _ => unreachable!(), 33 | }, 34 | _ => unreachable!(), 35 | }, 36 | true, 37 | ) 38 | } else { 39 | (typ, false) 40 | } 41 | } 42 | _ => (typ, false), 43 | }; 44 | let (_typ, is_vec) = match typ { 45 | Type::Path(ref path) => { 46 | let last = path.path.segments.last().unwrap(); 47 | if last.ident == "Vec" { 48 | ( 49 | match &last.arguments { 50 | PathArguments::AngleBracketed(arg) => match &arg.args[0] { 51 | GenericArgument::Type(typ) => typ, 52 | _ => unreachable!(), 53 | }, 54 | _ => unreachable!(), 55 | }, 56 | true, 57 | ) 58 | } else { 59 | (typ, false) 60 | } 61 | } 62 | _ => (typ, false), 63 | }; 64 | TypeInfo { is_arc, is_vec } 65 | } 66 | 67 | fn build_derive(name: Ident, data: Data) -> TokenStream { 68 | match data { 69 | Data::Struct(DataStruct { fields, .. }) => build_derive_struct(name, fields), 70 | Data::Enum(DataEnum { variants, .. }) => { 71 | build_derive_enum(name, variants.into_iter().collect()) 72 | } 73 | _ => panic!(), 74 | } 75 | } 76 | 77 | fn build_derive_struct(name: Ident, fields: Fields) -> TokenStream { 78 | let fields: Vec<_> = fields 79 | .iter() 80 | .map(|it| (it.ident.clone(), parse_type(&it.ty))) 81 | .collect(); 82 | let read = struct_read(&fields); 83 | let write = struct_write(&fields, true); 84 | quote! { 85 | impl crate::BinaryData for #name { 86 | fn read_binary(r: &mut crate::BinaryReader<'_>) -> Result { 87 | Ok(Self { #read }) 88 | } 89 | 90 | fn write_binary(&self, w: &mut crate::BinaryWriter<'_>) -> Result<()> { 91 | #write 92 | Ok(()) 93 | } 94 | } 95 | } 96 | } 97 | 98 | fn build_derive_enum(name: Ident, variants: Vec) -> TokenStream { 99 | let read_arms = variants 100 | .iter() 101 | .enumerate() 102 | .map(|(i, it)| { 103 | let i = i as u8; 104 | let name = &it.ident; 105 | match &it.fields { 106 | Fields::Unit => quote! { #i => Self::#name }, 107 | Fields::Unnamed(fields) => { 108 | let fields = struct_read( 109 | &fields 110 | .unnamed 111 | .iter() 112 | .map(|it| (None, parse_type(&it.ty))) 113 | .collect::>(), 114 | ); 115 | quote! { #i => Self::#name(#fields) } 116 | } 117 | Fields::Named(fields) => { 118 | let fields = struct_read( 119 | &fields 120 | .named 121 | .iter() 122 | .map(|it| (it.ident.clone(), parse_type(&it.ty))) 123 | .collect::>(), 124 | ); 125 | quote! { #i => Self::#name { #fields } } 126 | } 127 | } 128 | }) 129 | .chain(std::iter::once( 130 | quote! { x => anyhow::bail!("invalid enum: {}", x) }, 131 | )); 132 | let write_arms = variants.iter().enumerate().map(|(i, it)| { 133 | let i = i as u8; 134 | let name = &it.ident; 135 | match &it.fields { 136 | Fields::Unit => quote! { Self::#name => w.write_val(#i)? }, 137 | Fields::Unnamed(fields) => { 138 | let names: Vec<_> = (0..fields.unnamed.len()) 139 | .map(|i| { 140 | let ident = Ident::new(&format!("_{i}"), Span::call_site()); 141 | quote! { #ident } 142 | }) 143 | .collect(); 144 | let writes = struct_write( 145 | &fields 146 | .unnamed 147 | .iter() 148 | .map(|it| (None, parse_type(&it.ty))) 149 | .collect::>(), 150 | false, 151 | ); 152 | quote! { Self::#name(#(#names,)*) => { w.write_val(#i)?; #writes } } 153 | } 154 | Fields::Named(fields) => { 155 | let names: Vec<_> = fields 156 | .named 157 | .iter() 158 | .map(|it| { 159 | let ident = it.ident.clone().unwrap(); 160 | quote! { #ident } 161 | }) 162 | .collect(); 163 | let writes = struct_write( 164 | &fields 165 | .named 166 | .iter() 167 | .map(|it| (it.ident.clone(), parse_type(&it.ty))) 168 | .collect::>(), 169 | false, 170 | ); 171 | quote! { Self::#name { #(#names,)* } => { w.write_val(#i)?; #writes } } 172 | } 173 | } 174 | }); 175 | quote! { 176 | impl crate::BinaryData for #name { 177 | fn read_binary(r: &mut crate::BinaryReader<'_>) -> Result { 178 | Ok(match r.read::()? { 179 | #(#read_arms,)* 180 | }) 181 | } 182 | 183 | fn write_binary(&self, w: &mut crate::BinaryWriter<'_>) -> Result<()> { 184 | match self { 185 | #(#write_arms,)* 186 | } 187 | Ok(()) 188 | } 189 | } 190 | } 191 | } 192 | 193 | fn struct_read(fields: &[(Option, TypeInfo)]) -> TokenStream { 194 | let fields = fields.iter().map(|(name, typ)| field_read(name, typ)); 195 | quote! { #(#fields,)* } 196 | } 197 | 198 | fn field_read(name: &Option, typ: &TypeInfo) -> TokenStream { 199 | let val = match (typ.is_arc, typ.is_vec) { 200 | (false, false) => quote! { r.read()? }, 201 | (false, true) => quote! { r.array()? }, 202 | (true, false) => quote! { r.read()?.into() }, 203 | (true, true) => quote! { r.array()?.into() }, 204 | }; 205 | if let Some(name) = name { 206 | quote! { #name: #val } 207 | } else { 208 | val 209 | } 210 | } 211 | 212 | fn struct_write(fields: &[(Option, TypeInfo)], use_self: bool) -> TokenStream { 213 | fields 214 | .iter() 215 | .enumerate() 216 | .map(|(i, (name, typ))| { 217 | field_write( 218 | if use_self { 219 | let i = i as u8; 220 | if let Some(name) = name { 221 | quote! { &self.#name } 222 | } else { 223 | quote! { &self.#i } 224 | } 225 | } else { 226 | let name = name 227 | .clone() 228 | .unwrap_or(Ident::new(&format!("_{i}"), Span::call_site())); 229 | quote! { #name } 230 | }, 231 | typ, 232 | ) 233 | }) 234 | .collect() 235 | } 236 | 237 | fn field_write(field: TokenStream, typ: &TypeInfo) -> TokenStream { 238 | if typ.is_vec { 239 | quote! { w.array(#field)?; } 240 | } else { 241 | quote! { w.write(#field)?; } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /phira-mp-common/src/bin.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, hash::Hash}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use byteorder::{ByteOrder, LittleEndian as LE}; 5 | use chrono::{DateTime, TimeZone, Utc}; 6 | use tap::TapFallible; 7 | use uuid::Uuid; 8 | 9 | pub trait BinaryData: Sized { 10 | fn read_binary(r: &mut BinaryReader<'_>) -> Result; 11 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()>; 12 | } 13 | 14 | pub struct BinaryReader<'a>(&'a [u8], usize); 15 | 16 | impl<'a> BinaryReader<'a> { 17 | pub fn new(data: &'a [u8]) -> Self { 18 | Self(data, 0) 19 | } 20 | 21 | pub fn array(&mut self) -> Result> { 22 | (0..self.uleb()?).map(|_| self.read()).collect() 23 | } 24 | 25 | pub fn byte(&mut self) -> Result { 26 | self.0 27 | .get(self.1) 28 | .ok_or_else(|| anyhow!("unexpected EOF")) 29 | .tap_ok(|_| self.1 += 1) 30 | .copied() 31 | } 32 | 33 | pub fn take(&mut self, n: usize) -> Result<&'a [u8]> { 34 | self.0 35 | .get(self.1..(self.1 + n)) 36 | .ok_or_else(|| anyhow!("unexpected EOF")) 37 | .tap_ok(|_| self.1 += n) 38 | } 39 | 40 | pub fn read(&mut self) -> Result { 41 | T::read_binary(self) 42 | } 43 | 44 | pub fn uleb(&mut self) -> Result { 45 | let mut result = 0; 46 | let mut shift = 0; 47 | loop { 48 | let byte = self.read::()?; 49 | result |= ((byte & 0x7f) as u64) << shift; 50 | if byte & 0x80 == 0 { 51 | break Ok(result); 52 | } 53 | shift += 7; 54 | } 55 | } 56 | } 57 | 58 | pub struct BinaryWriter<'a>(&'a mut Vec); 59 | 60 | impl<'a> BinaryWriter<'a> { 61 | pub fn new(vec: &'a mut Vec) -> Self { 62 | Self(vec) 63 | } 64 | 65 | pub fn array(&mut self, v: &[T]) -> Result<()> { 66 | self.uleb(v.len() as _)?; 67 | for element in v { 68 | element.write_binary(self)?; 69 | } 70 | Ok(()) 71 | } 72 | 73 | #[inline] 74 | pub fn write(&mut self, v: &T) -> Result<()> { 75 | v.write_binary(self) 76 | } 77 | 78 | #[inline] 79 | pub fn write_val(&mut self, v: T) -> Result<()> { 80 | v.write_binary(self) 81 | } 82 | 83 | pub fn uleb(&mut self, mut v: u64) -> Result<()> { 84 | loop { 85 | let mut byte = (v & 0x7f) as u8; 86 | v >>= 7; 87 | if v != 0 { 88 | byte |= 0x80; 89 | } 90 | self.write_val(byte)?; 91 | if v == 0 { 92 | break Ok(()); 93 | } 94 | } 95 | } 96 | } 97 | 98 | impl BinaryData for () { 99 | fn read_binary(_r: &mut BinaryReader<'_>) -> Result { 100 | Ok(()) 101 | } 102 | 103 | fn write_binary(&self, _w: &mut BinaryWriter<'_>) -> Result<()> { 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl BinaryData for i8 { 109 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 110 | Ok(r.byte()? as i8) 111 | } 112 | 113 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 114 | w.0.push(*self as u8); 115 | Ok(()) 116 | } 117 | } 118 | 119 | impl BinaryData for u8 { 120 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 121 | r.byte() 122 | } 123 | 124 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 125 | w.0.push(*self); 126 | Ok(()) 127 | } 128 | } 129 | 130 | impl BinaryData for u16 { 131 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 132 | Ok(LE::read_u16(r.take(2)?)) 133 | } 134 | 135 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 136 | w.0.extend_from_slice(&self.to_le_bytes()); 137 | Ok(()) 138 | } 139 | } 140 | 141 | impl BinaryData for u32 { 142 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 143 | Ok(LE::read_u32(r.take(4)?)) 144 | } 145 | 146 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 147 | w.0.extend_from_slice(&self.to_le_bytes()); 148 | Ok(()) 149 | } 150 | } 151 | 152 | impl BinaryData for u64 { 153 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 154 | Ok(LE::read_u64(r.take(8)?)) 155 | } 156 | 157 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 158 | w.0.extend_from_slice(&self.to_le_bytes()); 159 | Ok(()) 160 | } 161 | } 162 | 163 | impl BinaryData for i32 { 164 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 165 | Ok(LE::read_i32(r.take(4)?)) 166 | } 167 | 168 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 169 | w.0.extend_from_slice(&self.to_le_bytes()); 170 | Ok(()) 171 | } 172 | } 173 | 174 | impl BinaryData for i64 { 175 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 176 | Ok(LE::read_i64(r.take(8)?)) 177 | } 178 | 179 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 180 | w.0.extend_from_slice(&self.to_le_bytes()); 181 | Ok(()) 182 | } 183 | } 184 | 185 | impl BinaryData for bool { 186 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 187 | Ok(r.byte()? == 1) 188 | } 189 | 190 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 191 | w.write_val(*self as u8) 192 | } 193 | } 194 | 195 | impl BinaryData for f32 { 196 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 197 | Ok(LE::read_f32(r.take(4)?)) 198 | } 199 | 200 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 201 | w.0.extend_from_slice(&self.to_le_bytes()); 202 | Ok(()) 203 | } 204 | } 205 | 206 | impl BinaryData for String { 207 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 208 | let len = r.uleb()? as usize; 209 | Ok(String::from_utf8_lossy(r.take(len)?).into_owned()) 210 | } 211 | 212 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 213 | w.uleb(self.len() as _)?; 214 | w.0.extend_from_slice(self.as_bytes()); 215 | Ok(()) 216 | } 217 | } 218 | 219 | impl BinaryData for (A, B) { 220 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 221 | Ok((r.read()?, r.read()?)) 222 | } 223 | 224 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 225 | w.write(&self.0)?; 226 | w.write(&self.1)?; 227 | Ok(()) 228 | } 229 | } 230 | 231 | impl BinaryData for Option { 232 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 233 | Ok(if r.read::()? { 234 | Some(r.read()?) 235 | } else { 236 | None 237 | }) 238 | } 239 | 240 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 241 | match self { 242 | Some(val) => { 243 | w.write_val(true)?; 244 | w.write(val)?; 245 | } 246 | None => { 247 | w.write_val(false)?; 248 | } 249 | } 250 | Ok(()) 251 | } 252 | } 253 | 254 | impl BinaryData for Result { 255 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 256 | Ok(if r.read::()? { 257 | Ok(r.read()?) 258 | } else { 259 | Err(r.read()?) 260 | }) 261 | } 262 | 263 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 264 | match self { 265 | Ok(val) => { 266 | w.write_val(true)?; 267 | w.write(val)?; 268 | } 269 | Err(err) => { 270 | w.write_val(false)?; 271 | w.write(err)?; 272 | } 273 | } 274 | Ok(()) 275 | } 276 | } 277 | 278 | impl BinaryData for Vec { 279 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 280 | r.array() 281 | } 282 | 283 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 284 | w.array(self) 285 | } 286 | } 287 | 288 | impl BinaryData for HashMap { 289 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 290 | (0..r.uleb()?).map(|_| r.read::<(K, V)>()).collect() 291 | } 292 | 293 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 294 | w.uleb(self.len() as _)?; 295 | for (k, v) in self { 296 | k.write_binary(w)?; 297 | v.write_binary(w)?; 298 | } 299 | Ok(()) 300 | } 301 | } 302 | 303 | impl BinaryData for Uuid { 304 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 305 | let low = r.read()?; 306 | let high = r.read()?; 307 | Ok(Self::from_u64_pair(high, low)) 308 | } 309 | 310 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 311 | let (high, low) = self.as_u64_pair(); 312 | w.write_val(low)?; 313 | w.write_val(high)?; 314 | Ok(()) 315 | } 316 | } 317 | 318 | impl BinaryData for DateTime { 319 | fn read_binary(r: &mut BinaryReader<'_>) -> Result { 320 | Utc.timestamp_millis_opt(r.read::()?) 321 | .single() 322 | .ok_or_else(|| anyhow!("invalid timestamp")) 323 | } 324 | 325 | fn write_binary(&self, w: &mut BinaryWriter<'_>) -> Result<()> { 326 | w.write_val(self.timestamp_millis()) 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /phira-mp-server/src/room.rs: -------------------------------------------------------------------------------- 1 | use crate::{Chart, Record, User}; 2 | use anyhow::{bail, Result}; 3 | use phira_mp_common::{ClientRoomState, Message, RoomId, RoomState, ServerCommand}; 4 | use rand::{seq::SliceRandom, thread_rng}; 5 | use std::{ 6 | collections::{HashMap, HashSet}, 7 | ops::Deref, 8 | sync::{ 9 | atomic::{AtomicBool, Ordering}, 10 | Arc, Weak, 11 | }, 12 | }; 13 | use tokio::sync::RwLock; 14 | use tracing::{debug, info}; 15 | 16 | const ROOM_MAX_USERS: usize = 8; 17 | 18 | #[derive(Default, Debug)] 19 | pub enum InternalRoomState { 20 | #[default] 21 | SelectChart, 22 | WaitForReady { 23 | started: HashSet, 24 | }, 25 | Playing { 26 | results: HashMap, 27 | aborted: HashSet, 28 | }, 29 | } 30 | 31 | impl InternalRoomState { 32 | pub fn to_client(&self, chart: Option) -> RoomState { 33 | match self { 34 | Self::SelectChart => RoomState::SelectChart(chart), 35 | Self::WaitForReady { .. } => RoomState::WaitingForReady, 36 | Self::Playing { .. } => RoomState::Playing, 37 | } 38 | } 39 | } 40 | 41 | pub struct Room { 42 | pub id: RoomId, 43 | pub host: RwLock>, 44 | pub state: RwLock, 45 | 46 | pub live: AtomicBool, 47 | pub locked: AtomicBool, 48 | pub cycle: AtomicBool, 49 | 50 | users: RwLock>>, 51 | monitors: RwLock>>, 52 | pub chart: RwLock>, 53 | } 54 | 55 | impl Room { 56 | pub fn new(id: RoomId, host: Weak) -> Self { 57 | Self { 58 | id, 59 | host: host.clone().into(), 60 | state: RwLock::default(), 61 | 62 | live: AtomicBool::new(false), 63 | locked: AtomicBool::new(false), 64 | cycle: AtomicBool::new(false), 65 | 66 | users: vec![host].into(), 67 | monitors: Vec::new().into(), 68 | chart: RwLock::default(), 69 | } 70 | } 71 | 72 | pub fn is_live(&self) -> bool { 73 | self.live.load(Ordering::SeqCst) 74 | } 75 | 76 | pub fn is_locked(&self) -> bool { 77 | self.locked.load(Ordering::SeqCst) 78 | } 79 | 80 | pub fn is_cycle(&self) -> bool { 81 | self.cycle.load(Ordering::SeqCst) 82 | } 83 | 84 | pub async fn client_room_state(&self) -> RoomState { 85 | self.state 86 | .read() 87 | .await 88 | .to_client(self.chart.read().await.as_ref().map(|it| it.id)) 89 | } 90 | 91 | pub async fn client_state(&self, user: &User) -> ClientRoomState { 92 | ClientRoomState { 93 | id: self.id.clone(), 94 | state: self.client_room_state().await, 95 | live: self.is_live(), 96 | locked: self.is_locked(), 97 | cycle: self.is_cycle(), 98 | is_host: self.check_host(user).await.is_ok(), 99 | is_ready: matches!(&*self.state.read().await, InternalRoomState::WaitForReady { started } if started.contains(&user.id)), 100 | users: self 101 | .users 102 | .read() 103 | .await 104 | .iter() 105 | .chain(self.monitors.read().await.iter()) 106 | .filter_map(|it| it.upgrade().map(|it| (it.id, it.to_info()))) 107 | .collect(), 108 | } 109 | } 110 | 111 | pub async fn on_state_change(&self) { 112 | self.broadcast(ServerCommand::ChangeState(self.client_room_state().await)) 113 | .await; 114 | } 115 | 116 | pub async fn add_user(&self, user: Weak, monitor: bool) -> bool { 117 | if monitor { 118 | let mut guard = self.monitors.write().await; 119 | guard.retain(|it| it.strong_count() > 0); 120 | guard.push(user); 121 | true 122 | } else { 123 | let mut guard = self.users.write().await; 124 | guard.retain(|it| it.strong_count() > 0); 125 | if guard.len() >= ROOM_MAX_USERS { 126 | false 127 | } else { 128 | guard.push(user); 129 | true 130 | } 131 | } 132 | } 133 | 134 | pub async fn users(&self) -> Vec> { 135 | self.users 136 | .read() 137 | .await 138 | .iter() 139 | .filter_map(|it| it.upgrade()) 140 | .collect() 141 | } 142 | 143 | pub async fn monitors(&self) -> Vec> { 144 | self.monitors 145 | .read() 146 | .await 147 | .iter() 148 | .filter_map(|it| it.upgrade()) 149 | .collect() 150 | } 151 | 152 | pub async fn check_host(&self, user: &User) -> Result<()> { 153 | if self.host.read().await.upgrade().map(|it| it.id) != Some(user.id) { 154 | bail!("only host can do this"); 155 | } 156 | Ok(()) 157 | } 158 | 159 | #[inline] 160 | pub async fn send(&self, msg: Message) { 161 | self.broadcast(ServerCommand::Message(msg)).await; 162 | } 163 | 164 | pub async fn broadcast(&self, cmd: ServerCommand) { 165 | debug!("broadcast {cmd:?}"); 166 | for session in self 167 | .users() 168 | .await 169 | .into_iter() 170 | .chain(self.monitors().await.into_iter()) 171 | { 172 | session.try_send(cmd.clone()).await; 173 | } 174 | } 175 | 176 | pub async fn broadcast_monitors(&self, cmd: ServerCommand) { 177 | for session in self.monitors().await { 178 | session.try_send(cmd.clone()).await; 179 | } 180 | } 181 | 182 | #[inline] 183 | pub async fn send_as(&self, user: &User, content: String) { 184 | self.send(Message::Chat { 185 | user: user.id, 186 | content, 187 | }) 188 | .await; 189 | } 190 | 191 | /// Return: should the room be dropped 192 | #[must_use] 193 | pub async fn on_user_leave(&self, user: &User) -> bool { 194 | self.send(Message::LeaveRoom { 195 | user: user.id, 196 | name: user.name.clone(), 197 | }) 198 | .await; 199 | *user.room.write().await = None; 200 | (if user.monitor.load(Ordering::SeqCst) { 201 | &self.monitors 202 | } else { 203 | &self.users 204 | }) 205 | .write() 206 | .await 207 | .retain(|it| it.upgrade().map_or(false, |it| it.id != user.id)); 208 | if self.check_host(user).await.is_ok() { 209 | info!("host disconnected!"); 210 | let users = self.users().await; 211 | if users.is_empty() { 212 | info!("room users all disconnected, dropping room"); 213 | return true; 214 | } else { 215 | let user = users.choose(&mut thread_rng()).unwrap(); 216 | debug!("selected {} as host", user.id); 217 | *self.host.write().await = Arc::downgrade(user); 218 | self.send(Message::NewHost { user: user.id }).await; 219 | user.try_send(ServerCommand::ChangeHost(true)).await; 220 | } 221 | } 222 | self.check_all_ready().await; 223 | false 224 | } 225 | 226 | pub async fn reset_game_time(&self) { 227 | for user in self.users().await { 228 | user.game_time 229 | .store(f32::NEG_INFINITY.to_bits(), Ordering::SeqCst); 230 | } 231 | } 232 | 233 | pub async fn check_all_ready(&self) { 234 | let guard = self.state.read().await; 235 | match guard.deref() { 236 | InternalRoomState::WaitForReady { started } => { 237 | if self 238 | .users() 239 | .await 240 | .into_iter() 241 | .chain(self.monitors().await.into_iter()) 242 | .all(|it| started.contains(&it.id)) 243 | { 244 | drop(guard); 245 | info!(room = self.id.to_string(), "game start"); 246 | self.send(Message::StartPlaying).await; 247 | self.reset_game_time().await; 248 | *self.state.write().await = InternalRoomState::Playing { 249 | results: HashMap::new(), 250 | aborted: HashSet::new(), 251 | }; 252 | self.on_state_change().await; 253 | } 254 | } 255 | InternalRoomState::Playing { results, aborted } => { 256 | if self 257 | .users() 258 | .await 259 | .into_iter() 260 | .all(|it| results.contains_key(&it.id) || aborted.contains(&it.id)) 261 | { 262 | drop(guard); 263 | // TODO print results 264 | self.send(Message::GameEnd).await; 265 | // dbg!(2); 266 | *self.state.write().await = InternalRoomState::SelectChart; 267 | // dbg!(3); 268 | if self.is_cycle() { 269 | debug!(room = self.id.to_string(), "cycling"); 270 | let host = Weak::clone(&*self.host.read().await); 271 | let new_host = { 272 | let users = self.users().await; 273 | let index = users 274 | .iter() 275 | .position(|it| host.ptr_eq(&Arc::downgrade(it))) 276 | .map(|it| (it + 1) % users.len()) 277 | .unwrap_or_default(); 278 | users.into_iter().nth(index).unwrap() 279 | }; 280 | *self.host.write().await = Arc::downgrade(&new_host); 281 | self.send(Message::NewHost { user: new_host.id }).await; 282 | if let Some(old) = host.upgrade() { 283 | old.try_send(ServerCommand::ChangeHost(false)).await; 284 | } 285 | new_host.try_send(ServerCommand::ChangeHost(true)).await; 286 | } 287 | self.on_state_change().await; 288 | } 289 | } 290 | _ => {} 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /phira-mp-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error, Result}; 2 | use dashmap::DashMap; 3 | use phira_mp_common::{ 4 | ClientCommand, ClientRoomState, JoinRoomResponse, JudgeEvent, Message, RoomId, RoomState, 5 | ServerCommand, Stream, TouchFrame, UserInfo, HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, 6 | }; 7 | use std::{ 8 | sync::{ 9 | atomic::{AtomicU8, Ordering}, 10 | Arc, 11 | }, 12 | time::{Duration, Instant}, 13 | }; 14 | use tokio::{ 15 | net::TcpStream, 16 | sync::{oneshot, Mutex, Notify, RwLock}, 17 | task::JoinHandle, 18 | time, 19 | }; 20 | use tracing::{error, trace, warn}; 21 | 22 | type Callback = Mutex>>; 23 | type RCallback = Mutex>>>; 24 | 25 | pub const TIMEOUT: Duration = Duration::from_secs(7); 26 | 27 | pub struct LivePlayer { 28 | pub touch_frames: Mutex>, 29 | pub judge_events: Mutex>, 30 | } 31 | 32 | impl LivePlayer { 33 | pub fn new() -> Self { 34 | Self { 35 | touch_frames: Mutex::default(), 36 | judge_events: Mutex::default(), 37 | } 38 | } 39 | } 40 | 41 | struct State { 42 | delay: Mutex>, 43 | ping_notify: Notify, 44 | 45 | me: RwLock>, 46 | room: RwLock>, 47 | 48 | cb_authenticate: RCallback<(UserInfo, Option)>, 49 | cb_chat: RCallback<()>, 50 | cb_create_room: RCallback<()>, 51 | cb_join_room: RCallback, 52 | cb_leave_room: RCallback<()>, 53 | cb_lock_room: RCallback<()>, 54 | cb_cycle_room: RCallback<()>, 55 | cb_select_chart: RCallback<()>, 56 | cb_request_start: RCallback<()>, 57 | cb_ready: RCallback<()>, 58 | cb_cancel_ready: RCallback<()>, 59 | cb_played: RCallback<()>, 60 | cb_abort: RCallback<()>, 61 | 62 | live_players: DashMap>, 63 | messages: Mutex>, 64 | } 65 | 66 | impl State { 67 | pub fn live_player(&self, player: i32) -> Arc { 68 | Arc::clone( 69 | &self 70 | .live_players 71 | .entry(player) 72 | .or_insert_with(|| Arc::new(LivePlayer::new())), 73 | ) 74 | } 75 | } 76 | 77 | pub struct Client { 78 | state: Arc, 79 | 80 | stream: Arc>, 81 | 82 | ping_fail_count: Arc, 83 | ping_task_handle: JoinHandle<()>, 84 | } 85 | 86 | impl Client { 87 | pub async fn new(stream: TcpStream) -> Result { 88 | stream.set_nodelay(true)?; 89 | 90 | let state = Arc::new(State { 91 | delay: Mutex::default(), 92 | ping_notify: Notify::new(), 93 | 94 | me: RwLock::default(), 95 | room: RwLock::default(), 96 | 97 | cb_authenticate: Callback::default(), 98 | cb_chat: Callback::default(), 99 | cb_create_room: Callback::default(), 100 | cb_join_room: Callback::default(), 101 | cb_leave_room: Callback::default(), 102 | cb_lock_room: Callback::default(), 103 | cb_cycle_room: Callback::default(), 104 | cb_select_chart: Callback::default(), 105 | cb_request_start: Callback::default(), 106 | cb_ready: Callback::default(), 107 | cb_cancel_ready: Callback::default(), 108 | cb_played: Callback::default(), 109 | cb_abort: Callback::default(), 110 | 111 | live_players: DashMap::new(), 112 | messages: Mutex::default(), 113 | }); 114 | let stream = Arc::new( 115 | Stream::new( 116 | Some(1), 117 | stream, 118 | Box::new({ 119 | let state = Arc::clone(&state); 120 | move |_send_tx, cmd| process(Arc::clone(&state), cmd) 121 | }), 122 | ) 123 | .await?, 124 | ); 125 | 126 | let ping_fail_count = Arc::new(AtomicU8::default()); 127 | let ping_task_handle = tokio::spawn({ 128 | let ping_fail_count = Arc::clone(&ping_fail_count); 129 | let state = Arc::clone(&state); 130 | let stream = Arc::clone(&stream); 131 | async move { 132 | loop { 133 | time::sleep(HEARTBEAT_INTERVAL).await; 134 | 135 | let start = Instant::now(); 136 | if let Err(err) = stream.send(ClientCommand::Ping).await { 137 | error!("failed to send heartbeat: {err:?}"); 138 | } else if time::timeout(HEARTBEAT_TIMEOUT, state.ping_notify.notified()) 139 | .await 140 | .is_err() 141 | { 142 | warn!("heartbeat timeout"); 143 | ping_fail_count.fetch_add(1, Ordering::Relaxed); 144 | } else { 145 | ping_fail_count.store(0, Ordering::SeqCst); 146 | } 147 | let delay = start.elapsed(); 148 | *state.delay.lock().await = Some(delay); 149 | trace!("sent heartbeat, delay: {delay:?}"); 150 | } 151 | } 152 | }); 153 | 154 | Ok(Self { 155 | state, 156 | 157 | stream, 158 | 159 | ping_fail_count, 160 | ping_task_handle, 161 | }) 162 | } 163 | 164 | pub fn me(&self) -> Option { 165 | self.state.me.blocking_read().clone() 166 | } 167 | 168 | pub fn user_name(&self, id: i32) -> String { 169 | self.user_name_opt(id).unwrap_or_else(|| "?".to_owned()) 170 | } 171 | 172 | pub fn user_name_opt(&self, id: i32) -> Option { 173 | self.state 174 | .room 175 | .blocking_read() 176 | .as_ref() 177 | .and_then(|it| it.users.get(&id).map(|it| it.name.clone())) 178 | } 179 | 180 | pub fn blocking_take_messages(&self) -> Vec { 181 | self.state.messages.blocking_lock().drain(..).collect() 182 | } 183 | 184 | pub fn blocking_state(&self) -> Option { 185 | self.state.room.blocking_read().clone() 186 | } 187 | 188 | pub fn blocking_room_id(&self) -> Option { 189 | self.state 190 | .room 191 | .blocking_read() 192 | .as_ref() 193 | .map(|it| it.id.clone()) 194 | } 195 | 196 | pub fn blocking_room_state(&self) -> Option { 197 | self.state.room.blocking_read().as_ref().map(|it| it.state) 198 | } 199 | 200 | pub async fn room_state(&self) -> Option { 201 | self.state.room.read().await.as_ref().map(|it| it.state) 202 | } 203 | 204 | pub fn blocking_is_host(&self) -> Option { 205 | self.state 206 | .room 207 | .blocking_read() 208 | .as_ref() 209 | .map(|it| it.is_host) 210 | } 211 | 212 | pub fn blocking_is_ready(&self) -> Option { 213 | self.state 214 | .room 215 | .blocking_read() 216 | .as_ref() 217 | .map(|it| it.is_ready) 218 | } 219 | 220 | pub async fn ping(&self) -> Result { 221 | let start = Instant::now(); 222 | self.stream.send(ClientCommand::Ping).await?; 223 | time::timeout(HEARTBEAT_TIMEOUT, self.state.ping_notify.notified()) 224 | .await 225 | .context("heartbeat timeout")?; 226 | let delay = start.elapsed(); 227 | *self.state.delay.lock().await = Some(delay); 228 | Ok(delay) 229 | } 230 | 231 | pub fn delay(&self) -> Option { 232 | *self.state.delay.blocking_lock() 233 | } 234 | 235 | async fn rcall(&self, payload: ClientCommand, cb: &RCallback) -> Result { 236 | self.stream.send(payload).await?; 237 | let (tx, rx) = oneshot::channel(); 238 | *cb.lock().await = Some(tx); 239 | time::timeout(TIMEOUT, rx) 240 | .await 241 | .context("timeout")?? 242 | .map_err(Error::msg) 243 | } 244 | 245 | #[inline] 246 | pub async fn authenticate(&self, token: impl Into) -> Result<()> { 247 | let (me, room) = self 248 | .rcall( 249 | ClientCommand::Authenticate { 250 | token: token.into().try_into()?, 251 | }, 252 | &self.state.cb_authenticate, 253 | ) 254 | .await?; 255 | *self.state.me.write().await = Some(me); 256 | *self.state.room.write().await = room; 257 | Ok(()) 258 | } 259 | 260 | #[inline] 261 | pub async fn chat(&self, message: String) -> Result<()> { 262 | self.rcall( 263 | ClientCommand::Chat { 264 | message: message.try_into()?, 265 | }, 266 | &self.state.cb_chat, 267 | ) 268 | .await 269 | } 270 | 271 | #[inline] 272 | pub async fn create_room(&self, id: RoomId) -> Result<()> { 273 | self.rcall( 274 | ClientCommand::CreateRoom { id: id.clone() }, 275 | &self.state.cb_create_room, 276 | ) 277 | .await?; 278 | let me = self.state.me.read().await.clone().unwrap(); 279 | *self.state.room.write().await = Some(ClientRoomState { 280 | id, 281 | state: RoomState::default(), 282 | live: false, 283 | locked: false, 284 | cycle: false, 285 | is_host: true, 286 | is_ready: false, 287 | users: std::iter::once((me.id, me)).collect(), 288 | }); 289 | Ok(()) 290 | } 291 | 292 | #[inline] 293 | pub async fn join_room(&self, id: RoomId, monitor: bool) -> Result<()> { 294 | let resp = self 295 | .rcall( 296 | ClientCommand::JoinRoom { 297 | id: id.clone(), 298 | monitor, 299 | }, 300 | &self.state.cb_join_room, 301 | ) 302 | .await?; 303 | *self.state.room.write().await = Some(ClientRoomState { 304 | id, 305 | state: resp.state, 306 | live: resp.live, 307 | locked: false, 308 | cycle: false, 309 | is_host: false, 310 | is_ready: false, 311 | users: resp.users.into_iter().map(|it| (it.id, it)).collect(), 312 | }); 313 | Ok(()) 314 | } 315 | 316 | #[inline] 317 | pub async fn leave_room(&self) -> Result<()> { 318 | self.rcall(ClientCommand::LeaveRoom, &self.state.cb_leave_room) 319 | .await?; 320 | *self.state.room.write().await = None; 321 | Ok(()) 322 | } 323 | 324 | #[inline] 325 | pub async fn lock_room(&self, lock: bool) -> Result<()> { 326 | self.rcall(ClientCommand::LockRoom { lock }, &self.state.cb_lock_room) 327 | .await 328 | } 329 | 330 | #[inline] 331 | pub async fn cycle_room(&self, cycle: bool) -> Result<()> { 332 | self.rcall( 333 | ClientCommand::CycleRoom { cycle }, 334 | &self.state.cb_cycle_room, 335 | ) 336 | .await 337 | } 338 | 339 | #[inline] 340 | pub async fn select_chart(&self, id: i32) -> Result<()> { 341 | self.rcall( 342 | ClientCommand::SelectChart { id }, 343 | &self.state.cb_select_chart, 344 | ) 345 | .await 346 | } 347 | 348 | #[inline] 349 | pub async fn request_start(&self) -> Result<()> { 350 | self.rcall(ClientCommand::RequestStart, &self.state.cb_request_start) 351 | .await?; 352 | self.state.room.write().await.as_mut().unwrap().is_ready = true; 353 | Ok(()) 354 | } 355 | 356 | #[inline] 357 | pub async fn ready(&self) -> Result<()> { 358 | self.rcall(ClientCommand::Ready, &self.state.cb_ready) 359 | .await?; 360 | self.state.room.write().await.as_mut().unwrap().is_ready = true; 361 | Ok(()) 362 | } 363 | 364 | #[inline] 365 | pub async fn cancel_ready(&self) -> Result<()> { 366 | self.rcall(ClientCommand::CancelReady, &self.state.cb_cancel_ready) 367 | .await?; 368 | self.state.room.write().await.as_mut().unwrap().is_ready = false; 369 | Ok(()) 370 | } 371 | 372 | #[inline] 373 | pub async fn played(&self, id: i32) -> Result<()> { 374 | self.rcall(ClientCommand::Played { id }, &self.state.cb_played) 375 | .await 376 | } 377 | 378 | #[inline] 379 | pub async fn abort(&self) -> Result<()> { 380 | self.rcall(ClientCommand::Abort, &self.state.cb_abort).await 381 | } 382 | 383 | pub fn ping_fail_count(&self) -> u8 { 384 | self.ping_fail_count.load(Ordering::Relaxed) 385 | } 386 | 387 | pub async fn send(&self, payload: ClientCommand) -> Result<()> { 388 | self.stream.send(payload).await 389 | } 390 | 391 | pub fn blocking_send(&self, payload: ClientCommand) -> Result<()> { 392 | self.stream.blocking_send(payload) 393 | } 394 | 395 | #[inline] 396 | pub fn live_player(&self, player: i32) -> Arc { 397 | self.state.live_player(player) 398 | } 399 | } 400 | 401 | impl Drop for Client { 402 | fn drop(&mut self) { 403 | self.ping_task_handle.abort(); 404 | } 405 | } 406 | 407 | async fn process(state: Arc, cmd: ServerCommand) { 408 | async fn cb(cb: &Callback, res: T) { 409 | let _ = cb.lock().await.take().unwrap().send(res); 410 | } 411 | match cmd { 412 | ServerCommand::Pong => { 413 | state.ping_notify.notify_one(); 414 | } 415 | ServerCommand::Authenticate(res) => { 416 | cb(&state.cb_authenticate, res).await; 417 | } 418 | ServerCommand::Chat(res) => { 419 | cb(&state.cb_chat, res).await; 420 | } 421 | ServerCommand::Touches { player, frames } => { 422 | state 423 | .live_player(player) 424 | .touch_frames 425 | .lock() 426 | .await 427 | .extend(frames.iter().cloned()); 428 | } 429 | ServerCommand::Judges { player, judges } => { 430 | state 431 | .live_player(player) 432 | .judge_events 433 | .lock() 434 | .await 435 | .extend(judges.iter().cloned()); 436 | } 437 | ServerCommand::Message(msg) => { 438 | match msg { 439 | Message::LockRoom { lock } => { 440 | state.room.write().await.as_mut().unwrap().locked = lock; 441 | } 442 | Message::CycleRoom { cycle } => { 443 | state.room.write().await.as_mut().unwrap().cycle = cycle; 444 | } 445 | Message::LeaveRoom { user, .. } => { 446 | state 447 | .room 448 | .write() 449 | .await 450 | .as_mut() 451 | .unwrap() 452 | .users 453 | .remove(&user); 454 | } 455 | _ => {} 456 | } 457 | state.messages.lock().await.push(msg); 458 | } 459 | ServerCommand::ChangeState(room) => { 460 | state.live_players.clear(); 461 | let mut guard = state.room.write().await; 462 | let state = guard.as_mut().unwrap(); 463 | state.state = room; 464 | state.is_ready = state.is_host; 465 | } 466 | ServerCommand::ChangeHost(me_is_host) => { 467 | state.room.write().await.as_mut().unwrap().is_host = me_is_host; 468 | } 469 | 470 | ServerCommand::CreateRoom(res) => { 471 | cb(&state.cb_create_room, res).await; 472 | } 473 | ServerCommand::JoinRoom(res) => { 474 | cb(&state.cb_join_room, res).await; 475 | } 476 | ServerCommand::OnJoinRoom(user) => { 477 | if let Some(room) = state.room.write().await.as_mut() { 478 | room.live |= user.monitor; 479 | room.users.insert(user.id, user); 480 | } 481 | } 482 | ServerCommand::LeaveRoom(res) => { 483 | cb(&state.cb_leave_room, res).await; 484 | } 485 | ServerCommand::LockRoom(res) => { 486 | cb(&state.cb_lock_room, res).await; 487 | } 488 | ServerCommand::CycleRoom(res) => { 489 | cb(&state.cb_cycle_room, res).await; 490 | } 491 | ServerCommand::SelectChart(res) => { 492 | cb(&state.cb_select_chart, res).await; 493 | } 494 | ServerCommand::RequestStart(res) => { 495 | cb(&state.cb_request_start, res).await; 496 | } 497 | ServerCommand::Ready(res) => { 498 | cb(&state.cb_ready, res).await; 499 | } 500 | ServerCommand::CancelReady(res) => { 501 | cb(&state.cb_cancel_ready, res).await; 502 | } 503 | ServerCommand::Played(res) => { 504 | cb(&state.cb_played, res).await; 505 | } 506 | ServerCommand::Abort(res) => { 507 | cb(&state.cb_abort, res).await; 508 | } 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /phira-mp-server/src/session.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | l10n::{Language, LANGUAGE}, 3 | tl, Chart, InternalRoomState, Record, Room, ServerState, 4 | }; 5 | use anyhow::{anyhow, bail, Result}; 6 | use phira_mp_common::{ 7 | ClientCommand, JoinRoomResponse, Message, ServerCommand, Stream, UserInfo, 8 | HEARTBEAT_DISCONNECT_TIMEOUT, 9 | }; 10 | use serde::Deserialize; 11 | use std::{ 12 | collections::{hash_map::Entry, HashSet}, 13 | ops::DerefMut, 14 | sync::{ 15 | atomic::{AtomicBool, AtomicU32, Ordering}, 16 | Arc, Weak, 17 | }, 18 | time::{Duration, Instant}, 19 | }; 20 | use tokio::{ 21 | net::TcpStream, 22 | sync::{oneshot, Mutex, Notify, OnceCell, RwLock}, 23 | task::JoinHandle, 24 | time, 25 | }; 26 | use tracing::{debug, debug_span, error, info, trace, warn, Instrument}; 27 | use uuid::Uuid; 28 | 29 | const HOST: &str = "https://phira.5wyxi.com"; 30 | 31 | pub struct User { 32 | pub id: i32, 33 | pub name: String, 34 | pub lang: Language, 35 | 36 | pub server: Arc, 37 | pub session: RwLock>>, 38 | pub room: RwLock>>, 39 | 40 | pub monitor: AtomicBool, 41 | pub game_time: AtomicU32, 42 | 43 | pub dangle_mark: Mutex>>, 44 | } 45 | 46 | impl User { 47 | pub fn new(id: i32, name: String, lang: Language, server: Arc) -> Self { 48 | Self { 49 | id, 50 | name, 51 | lang, 52 | 53 | server, 54 | session: RwLock::default(), 55 | room: RwLock::default(), 56 | 57 | monitor: AtomicBool::default(), 58 | game_time: AtomicU32::default(), 59 | 60 | dangle_mark: Mutex::default(), 61 | } 62 | } 63 | 64 | pub fn to_info(&self) -> UserInfo { 65 | UserInfo { 66 | id: self.id, 67 | name: self.name.clone(), 68 | monitor: self.monitor.load(Ordering::SeqCst), 69 | } 70 | } 71 | 72 | pub fn can_monitor(&self) -> bool { 73 | self.server.config.monitors.contains(&self.id) 74 | } 75 | 76 | pub async fn set_session(&self, session: Weak) { 77 | *self.session.write().await = Some(session); 78 | *self.dangle_mark.lock().await = None; 79 | } 80 | 81 | pub async fn try_send(&self, cmd: ServerCommand) { 82 | if let Some(session) = self.session.read().await.as_ref().and_then(Weak::upgrade) { 83 | session.try_send(cmd).await; 84 | } else { 85 | warn!("sending {cmd:?} to dangling user {}", self.id); 86 | } 87 | } 88 | 89 | pub async fn dangle(self: Arc) { 90 | warn!(user = self.id, "user dangling"); 91 | let guard = self.room.read().await; 92 | let room = guard.as_ref().map(Arc::clone); 93 | drop(guard); 94 | if let Some(room) = room { 95 | let guard = room.state.read().await; 96 | if matches!(*guard, InternalRoomState::Playing { .. }) { 97 | warn!(user = self.id, "lost connection on playing, aborting"); 98 | self.server.users.write().await.remove(&self.id); 99 | drop(guard); 100 | if room.on_user_leave(&self).await { 101 | self.server.rooms.write().await.remove(&room.id); 102 | } 103 | return; 104 | } 105 | } 106 | let dangle_mark = Arc::new(()); 107 | *self.dangle_mark.lock().await = Some(Arc::clone(&dangle_mark)); 108 | tokio::spawn(async move { 109 | time::sleep(Duration::from_secs(10)).await; 110 | if Arc::strong_count(&dangle_mark) > 1 { 111 | let guard = self.room.read().await; 112 | let room = guard.as_ref().map(Arc::clone); 113 | drop(guard); 114 | if let Some(room) = room { 115 | self.server.users.write().await.remove(&self.id); 116 | if room.on_user_leave(&self).await { 117 | self.server.rooms.write().await.remove(&room.id); 118 | } 119 | } 120 | } 121 | }); 122 | } 123 | } 124 | 125 | pub struct Session { 126 | pub id: Uuid, 127 | pub stream: Stream, 128 | pub user: Arc, 129 | 130 | monitor_task_handle: JoinHandle<()>, 131 | } 132 | 133 | impl Session { 134 | pub async fn new(id: Uuid, stream: TcpStream, server: Arc) -> Result> { 135 | stream.set_nodelay(true)?; 136 | let this = Arc::new(OnceCell::>::new()); 137 | let this_inited = Arc::new(Notify::new()); 138 | let (tx, rx) = oneshot::channel::>(); 139 | let last_recv: Arc> = Arc::new(Mutex::new(Instant::now())); 140 | let stream = Stream::::new( 141 | None, 142 | stream, 143 | Box::new({ 144 | let this = Arc::clone(&this); 145 | let this_inited = Arc::clone(&this_inited); 146 | let mut tx = Some(tx); 147 | let server = Arc::clone(&server); 148 | let last_recv = Arc::clone(&last_recv); 149 | let waiting_for_authenticate = Arc::new(AtomicBool::new(true)); 150 | let panicked = Arc::new(AtomicBool::new(false)); 151 | move |send_tx, cmd| { 152 | let this = Arc::clone(&this); 153 | let this_inited = Arc::clone(&this_inited); 154 | let tx = tx.take(); 155 | let server = Arc::clone(&server); 156 | let last_recv = Arc::clone(&last_recv); 157 | let waiting_for_authenticate = Arc::clone(&waiting_for_authenticate); 158 | let panicked = Arc::clone(&panicked); 159 | async move { 160 | *last_recv.lock().await = Instant::now(); 161 | if panicked.load(Ordering::SeqCst) { 162 | return; 163 | } 164 | if matches!(cmd, ClientCommand::Ping) { 165 | let _ = send_tx.send(ServerCommand::Pong).await; 166 | return; 167 | } 168 | if waiting_for_authenticate.load(Ordering::SeqCst) { 169 | if let ClientCommand::Authenticate { token } = cmd { 170 | let Some(tx) = tx else { return }; 171 | let res: Result<()> = { 172 | let this = Arc::clone(&this); 173 | let server = Arc::clone(&server); 174 | async move { 175 | let token = token.into_inner(); 176 | if token.len() != 32 { 177 | bail!("invalid token"); 178 | } 179 | debug!("session {id}: authenticate {token}"); 180 | #[derive(Debug, Deserialize)] 181 | struct UserInfo { 182 | id: i32, 183 | name: String, 184 | language: String, 185 | } 186 | let resp: Result = async { 187 | Ok(reqwest::Client::new() 188 | .get(format!("{HOST}/me")) 189 | .header( 190 | reqwest::header::AUTHORIZATION, 191 | format!("Bearer {token}"), 192 | ) 193 | .send() 194 | .await? 195 | .error_for_status()? 196 | .json() 197 | .await?) 198 | } 199 | .await; 200 | let resp = match resp { 201 | Ok(resp) => resp, 202 | Err(err) => { 203 | warn!("failed to fetch info: {err:?}"); 204 | bail!("failed to fetch info"); 205 | } 206 | }; 207 | debug!("session {id} <- {resp:?}"); 208 | let mut users_guard = server.users.write().await; 209 | if let Some(user) = users_guard.get(&resp.id) { 210 | info!("reconnect"); 211 | let _ = tx.send(Arc::clone(user)); 212 | this_inited.notified().await; 213 | user.set_session(Arc::downgrade(this.get().unwrap())) 214 | .await; 215 | } else { 216 | let user = Arc::new(User::new( 217 | resp.id, 218 | resp.name, 219 | resp.language 220 | .parse() 221 | .map(Language) 222 | .unwrap_or_default(), 223 | Arc::clone(&server), 224 | )); 225 | let _ = tx.send(Arc::clone(&user)); 226 | this_inited.notified().await; 227 | user.set_session(Arc::downgrade(this.get().unwrap())) 228 | .await; 229 | users_guard.insert(resp.id, user); 230 | } 231 | Ok(()) 232 | } 233 | } 234 | .await; 235 | if let Err(err) = res { 236 | warn!("failed to authenticate: {err:?}"); 237 | let _ = send_tx 238 | .send(ServerCommand::Authenticate(Err(err.to_string()))) 239 | .await; 240 | panicked.store(true, Ordering::SeqCst); 241 | if let Err(err) = server.lost_con_tx.send(id).await { 242 | error!("failed to mark lost connection ({id}): {err:?}"); 243 | } 244 | } else { 245 | let user = &this.get().unwrap().user; 246 | let room_state = match user.room.read().await.as_ref() { 247 | Some(room) => Some(room.client_state(user).await), 248 | None => None, 249 | }; 250 | let _ = send_tx 251 | .send(ServerCommand::Authenticate(Ok(( 252 | user.to_info(), 253 | room_state, 254 | )))) 255 | .await; 256 | waiting_for_authenticate.store(false, Ordering::SeqCst); 257 | } 258 | return; 259 | } else { 260 | warn!("packet before authentication, ignoring: {cmd:?}"); 261 | return; 262 | } 263 | } 264 | let user = this.get().map(|it| Arc::clone(&it.user)).unwrap(); 265 | if let Some(resp) = LANGUAGE 266 | .scope(Arc::new(user.lang.clone()), process(user, cmd)) 267 | .await 268 | { 269 | if let Err(err) = send_tx.send(resp).await { 270 | error!( 271 | "failed to handle message, aborting connection {id}: {err:?}", 272 | ); 273 | panicked.store(true, Ordering::SeqCst); 274 | if let Err(err) = server.lost_con_tx.send(id).await { 275 | error!("failed to mark lost connection ({id}): {err:?}"); 276 | } 277 | } 278 | } 279 | } 280 | } 281 | }), 282 | ) 283 | .await?; 284 | let monitor_task_handle = tokio::spawn({ 285 | let last_recv = Arc::clone(&last_recv); 286 | async move { 287 | loop { 288 | let recv = *last_recv.lock().await; 289 | time::sleep_until((recv + HEARTBEAT_DISCONNECT_TIMEOUT).into()).await; 290 | 291 | if *last_recv.lock().await + HEARTBEAT_DISCONNECT_TIMEOUT > Instant::now() { 292 | continue; 293 | } 294 | 295 | if let Err(err) = server.lost_con_tx.send(id).await { 296 | error!("failed to mark lost connection ({id}): {err:?}"); 297 | } 298 | break; 299 | } 300 | } 301 | }); 302 | 303 | let user = rx.await?; 304 | 305 | let res = Arc::new(Self { 306 | id, 307 | stream, 308 | user, 309 | 310 | monitor_task_handle, 311 | }); 312 | let _ = this.set(Arc::clone(&res)); 313 | this_inited.notify_one(); 314 | Ok(res) 315 | } 316 | 317 | pub fn version(&self) -> u8 { 318 | self.stream.version() 319 | } 320 | 321 | pub fn name(&self) -> &str { 322 | &self.user.name 323 | } 324 | 325 | pub async fn try_send(&self, cmd: ServerCommand) { 326 | if let Err(err) = self.stream.send(cmd).await { 327 | error!("failed to deliver command to {}: {err:?}", self.id); 328 | } 329 | } 330 | } 331 | 332 | impl Drop for Session { 333 | fn drop(&mut self) { 334 | self.monitor_task_handle.abort(); 335 | } 336 | } 337 | 338 | async fn process(user: Arc, cmd: ClientCommand) -> Option { 339 | #[inline] 340 | fn err_to_str(result: Result) -> Result { 341 | result.map_err(|it| it.to_string()) 342 | } 343 | 344 | macro_rules! get_room { 345 | (~ $d:ident) => { 346 | let $d = match user.room.read().await.as_ref().map(Arc::clone) { 347 | Some(room) => room, 348 | None => { 349 | warn!("no room"); 350 | return None; 351 | } 352 | }; 353 | }; 354 | ($d:ident) => { 355 | let $d = user 356 | .room 357 | .read() 358 | .await 359 | .as_ref() 360 | .map(Arc::clone) 361 | .ok_or_else(|| anyhow!("no room"))?; 362 | }; 363 | ($d:ident, $($pt:tt)*) => { 364 | let $d = user 365 | .room 366 | .read() 367 | .await 368 | .as_ref() 369 | .map(Arc::clone) 370 | .ok_or_else(|| anyhow!("no room"))?; 371 | if !matches!(&*$d.state.read().await, $($pt)*) { 372 | bail!("invalid state"); 373 | } 374 | }; 375 | } 376 | match cmd { 377 | ClientCommand::Ping => unreachable!(), 378 | ClientCommand::Authenticate { .. } => Some(ServerCommand::Authenticate(Err( 379 | "repeated authenticate".to_owned(), 380 | ))), 381 | ClientCommand::Chat { message } => { 382 | let res: Result<()> = async move { 383 | get_room!(room); 384 | room.send_as(&user, message.into_inner()).await; 385 | Ok(()) 386 | } 387 | .await; 388 | Some(ServerCommand::Chat(err_to_str(res))) 389 | } 390 | ClientCommand::Touches { frames } => { 391 | get_room!(~ room); 392 | if room.is_live() { 393 | debug!("received {} touch events from {}", frames.len(), user.id); 394 | if let Some(frame) = frames.last() { 395 | user.game_time.store(frame.time.to_bits(), Ordering::SeqCst); 396 | } 397 | tokio::spawn(async move { 398 | room.broadcast_monitors(ServerCommand::Touches { 399 | player: user.id, 400 | frames, 401 | }) 402 | .await; 403 | }); 404 | } else { 405 | warn!("received touch events in non-live mode"); 406 | } 407 | None 408 | } 409 | ClientCommand::Judges { judges } => { 410 | get_room!(~ room); 411 | if room.is_live() { 412 | debug!("received {} judge events from {}", judges.len(), user.id); 413 | tokio::spawn(async move { 414 | room.broadcast_monitors(ServerCommand::Judges { 415 | player: user.id, 416 | judges, 417 | }) 418 | .await; 419 | }); 420 | } else { 421 | warn!("received judge events in non-live mode"); 422 | } 423 | None 424 | } 425 | ClientCommand::CreateRoom { id } => { 426 | let res: Result<()> = async move { 427 | let mut room_guard = user.room.write().await; 428 | if room_guard.is_some() { 429 | bail!("already in room"); 430 | } 431 | 432 | let mut map_guard = user.server.rooms.write().await; 433 | let room = Arc::new(Room::new(id.clone(), Arc::downgrade(&user))); 434 | match map_guard.entry(id.clone()) { 435 | Entry::Vacant(entry) => { 436 | entry.insert(Arc::clone(&room)); 437 | } 438 | Entry::Occupied(_) => { 439 | bail!(tl!("create-id-occupied")); 440 | } 441 | } 442 | room.send(Message::CreateRoom { user: user.id }).await; 443 | drop(map_guard); 444 | *room_guard = Some(room); 445 | 446 | info!(user = user.id, room = id.to_string(), "user create room"); 447 | Ok(()) 448 | } 449 | .await; 450 | Some(ServerCommand::CreateRoom(err_to_str(res))) 451 | } 452 | ClientCommand::JoinRoom { id, monitor } => { 453 | let res: Result = async move { 454 | let mut room_guard = user.room.write().await; 455 | if room_guard.is_some() { 456 | bail!("already in room"); 457 | } 458 | let room = user.server.rooms.read().await.get(&id).map(Arc::clone); 459 | let Some(room) = room else { bail!("room not found") }; 460 | if room.locked.load(Ordering::SeqCst) { 461 | bail!(tl!("join-room-locked")); 462 | } 463 | if !matches!(*room.state.read().await, InternalRoomState::SelectChart) { 464 | bail!(tl!("join-game-ongoing")); 465 | } 466 | if monitor && !user.can_monitor() { 467 | bail!(tl!("join-cant-monitor")); 468 | } 469 | if !room.add_user(Arc::downgrade(&user), monitor).await { 470 | bail!(tl!("join-room-full")); 471 | } 472 | info!( 473 | user = user.id, 474 | room = id.to_string(), 475 | monitor, 476 | "user join room" 477 | ); 478 | user.monitor.store(monitor, Ordering::SeqCst); 479 | if monitor && !room.live.fetch_or(true, Ordering::SeqCst) { 480 | info!(room = id.to_string(), "room goes live"); 481 | } 482 | room.broadcast(ServerCommand::OnJoinRoom(user.to_info())) 483 | .await; 484 | room.send(Message::JoinRoom { 485 | user: user.id, 486 | name: user.name.clone(), 487 | }) 488 | .await; 489 | *room_guard = Some(Arc::clone(&room)); 490 | Ok(JoinRoomResponse { 491 | state: room.client_room_state().await, 492 | users: room 493 | .users() 494 | .await 495 | .into_iter() 496 | .chain(room.monitors().await.into_iter()) 497 | .map(|it| it.to_info()) 498 | .collect(), 499 | live: room.is_live(), 500 | }) 501 | } 502 | .await; 503 | Some(ServerCommand::JoinRoom(err_to_str(res))) 504 | } 505 | ClientCommand::LeaveRoom => { 506 | let res: Result<()> = async move { 507 | get_room!(room); 508 | // TODO is this necessary? 509 | // if !matches!(*room.state.read().await, InternalRoomState::SelectChart) { 510 | // bail!("game ongoing, can't leave"); 511 | // } 512 | info!( 513 | user = user.id, 514 | room = room.id.to_string(), 515 | "user leave room" 516 | ); 517 | if room.on_user_leave(&user).await { 518 | user.server.rooms.write().await.remove(&room.id); 519 | } 520 | Ok(()) 521 | } 522 | .await; 523 | Some(ServerCommand::LeaveRoom(err_to_str(res))) 524 | } 525 | ClientCommand::LockRoom { lock } => { 526 | let res: Result<()> = async move { 527 | get_room!(room); 528 | room.check_host(&user).await?; 529 | info!( 530 | user = user.id, 531 | room = room.id.to_string(), 532 | lock, 533 | "lock room" 534 | ); 535 | room.locked.store(lock, Ordering::SeqCst); 536 | room.send(Message::LockRoom { lock }).await; 537 | Ok(()) 538 | } 539 | .await; 540 | Some(ServerCommand::LockRoom(err_to_str(res))) 541 | } 542 | ClientCommand::CycleRoom { cycle } => { 543 | let res: Result<()> = async move { 544 | get_room!(room); 545 | room.check_host(&user).await?; 546 | info!( 547 | user = user.id, 548 | room = room.id.to_string(), 549 | cycle, 550 | "cycle room" 551 | ); 552 | room.cycle.store(cycle, Ordering::SeqCst); 553 | room.send(Message::CycleRoom { cycle }).await; 554 | Ok(()) 555 | } 556 | .await; 557 | Some(ServerCommand::CycleRoom(err_to_str(res))) 558 | } 559 | ClientCommand::SelectChart { id } => { 560 | let res: Result<()> = async move { 561 | get_room!(room, InternalRoomState::SelectChart); 562 | room.check_host(&user).await?; 563 | let span = debug_span!( 564 | "select chart", 565 | user = user.id, 566 | room = room.id.to_string(), 567 | chart = id, 568 | ); 569 | async move { 570 | trace!("fetch"); 571 | let res: Chart = reqwest::get(format!("{HOST}/chart/{id}")) 572 | .await? 573 | .error_for_status()? 574 | .json() 575 | .await?; 576 | debug!("chart is {res:?}"); 577 | room.send(Message::SelectChart { 578 | user: user.id, 579 | name: res.name.clone(), 580 | id: res.id, 581 | }) 582 | .await; 583 | *room.chart.write().await = Some(res); 584 | room.on_state_change().await; 585 | Ok(()) 586 | } 587 | .instrument(span) 588 | .await 589 | } 590 | .await; 591 | Some(ServerCommand::SelectChart(err_to_str(res))) 592 | } 593 | 594 | ClientCommand::RequestStart => { 595 | let res: Result<()> = async move { 596 | get_room!(room, InternalRoomState::SelectChart); 597 | room.check_host(&user).await?; 598 | if room.chart.read().await.is_none() { 599 | bail!(tl!("start-no-chart-selected")); 600 | } 601 | debug!(room = room.id.to_string(), "room wait for ready"); 602 | room.reset_game_time().await; 603 | room.send(Message::GameStart { user: user.id }).await; 604 | *room.state.write().await = InternalRoomState::WaitForReady { 605 | started: std::iter::once(user.id).collect::>(), 606 | }; 607 | room.on_state_change().await; 608 | room.check_all_ready().await; 609 | Ok(()) 610 | } 611 | .await; 612 | Some(ServerCommand::RequestStart(err_to_str(res))) 613 | } 614 | ClientCommand::Ready => { 615 | let res: Result<()> = async move { 616 | get_room!(room); 617 | let mut guard = room.state.write().await; 618 | if let InternalRoomState::WaitForReady { started } = guard.deref_mut() { 619 | if !started.insert(user.id) { 620 | bail!("already ready"); 621 | } 622 | room.send(Message::Ready { user: user.id }).await; 623 | drop(guard); 624 | room.check_all_ready().await; 625 | } 626 | Ok(()) 627 | } 628 | .await; 629 | Some(ServerCommand::Ready(err_to_str(res))) 630 | } 631 | ClientCommand::CancelReady => { 632 | let res: Result<()> = async move { 633 | get_room!(room); 634 | let mut guard = room.state.write().await; 635 | if let InternalRoomState::WaitForReady { started } = guard.deref_mut() { 636 | if !started.remove(&user.id) { 637 | bail!("not ready"); 638 | } 639 | if room.check_host(&user).await.is_ok() { 640 | room.send(Message::CancelGame { user: user.id }).await; 641 | *guard = InternalRoomState::SelectChart; 642 | drop(guard); 643 | room.on_state_change().await; 644 | } else { 645 | room.send(Message::CancelReady { user: user.id }).await; 646 | } 647 | } 648 | Ok(()) 649 | } 650 | .await; 651 | Some(ServerCommand::CancelReady(err_to_str(res))) 652 | } 653 | ClientCommand::Played { id } => { 654 | let res: Result<()> = async move { 655 | get_room!(room); 656 | let res: Record = reqwest::get(format!("{HOST}/record/{id}")) 657 | .await? 658 | .error_for_status()? 659 | .json() 660 | .await?; 661 | if res.player != user.id { 662 | bail!("invalid record"); 663 | } 664 | debug!( 665 | room = room.id.to_string(), 666 | user = user.id, 667 | "user played: {res:?}" 668 | ); 669 | room.send(Message::Played { 670 | user: user.id, 671 | score: res.score, 672 | accuracy: res.accuracy, 673 | full_combo: res.full_combo, 674 | }) 675 | .await; 676 | let mut guard = room.state.write().await; 677 | if let InternalRoomState::Playing { results, aborted } = guard.deref_mut() { 678 | if aborted.contains(&user.id) { 679 | bail!("aborted"); 680 | } 681 | if results.insert(user.id, res).is_some() { 682 | bail!("already uploaded"); 683 | } 684 | drop(guard); 685 | room.check_all_ready().await; 686 | } 687 | Ok(()) 688 | } 689 | .await; 690 | Some(ServerCommand::Played(err_to_str(res))) 691 | } 692 | ClientCommand::Abort => { 693 | let res: Result<()> = async move { 694 | get_room!(room); 695 | let mut guard = room.state.write().await; 696 | if let InternalRoomState::Playing { results, aborted } = guard.deref_mut() { 697 | if results.contains_key(&user.id) { 698 | bail!("already uploaded"); 699 | } 700 | if !aborted.insert(user.id) { 701 | bail!("aborted"); 702 | } 703 | drop(guard); 704 | room.send(Message::Abort { user: user.id }).await; 705 | room.check_all_ready().await; 706 | } 707 | Ok(()) 708 | } 709 | .await; 710 | Some(ServerCommand::Abort(err_to_str(res))) 711 | } 712 | } 713 | } 714 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "android-tzdata" 43 | version = "0.1.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 46 | 47 | [[package]] 48 | name = "android_system_properties" 49 | version = "0.1.5" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 52 | dependencies = [ 53 | "libc", 54 | ] 55 | 56 | [[package]] 57 | name = "anstream" 58 | version = "0.6.15" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 61 | dependencies = [ 62 | "anstyle", 63 | "anstyle-parse", 64 | "anstyle-query", 65 | "anstyle-wincon", 66 | "colorchoice", 67 | "is_terminal_polyfill", 68 | "utf8parse", 69 | ] 70 | 71 | [[package]] 72 | name = "anstyle" 73 | version = "1.0.8" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 76 | 77 | [[package]] 78 | name = "anstyle-parse" 79 | version = "0.2.5" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 82 | dependencies = [ 83 | "utf8parse", 84 | ] 85 | 86 | [[package]] 87 | name = "anstyle-query" 88 | version = "1.1.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 91 | dependencies = [ 92 | "windows-sys 0.52.0", 93 | ] 94 | 95 | [[package]] 96 | name = "anstyle-wincon" 97 | version = "3.0.4" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 100 | dependencies = [ 101 | "anstyle", 102 | "windows-sys 0.52.0", 103 | ] 104 | 105 | [[package]] 106 | name = "anyhow" 107 | version = "1.0.86" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 110 | dependencies = [ 111 | "backtrace", 112 | ] 113 | 114 | [[package]] 115 | name = "autocfg" 116 | version = "1.3.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 119 | 120 | [[package]] 121 | name = "backtrace" 122 | version = "0.3.73" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 125 | dependencies = [ 126 | "addr2line", 127 | "cc", 128 | "cfg-if", 129 | "libc", 130 | "miniz_oxide", 131 | "object", 132 | "rustc-demangle", 133 | ] 134 | 135 | [[package]] 136 | name = "base64" 137 | version = "0.21.7" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 140 | 141 | [[package]] 142 | name = "bitflags" 143 | version = "1.3.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 146 | 147 | [[package]] 148 | name = "bitflags" 149 | version = "2.6.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 152 | 153 | [[package]] 154 | name = "bumpalo" 155 | version = "3.16.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 158 | 159 | [[package]] 160 | name = "byteorder" 161 | version = "1.5.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 164 | 165 | [[package]] 166 | name = "bytes" 167 | version = "1.7.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 170 | 171 | [[package]] 172 | name = "cc" 173 | version = "1.1.15" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 176 | dependencies = [ 177 | "shlex", 178 | ] 179 | 180 | [[package]] 181 | name = "cfg-if" 182 | version = "1.0.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 185 | 186 | [[package]] 187 | name = "chrono" 188 | version = "0.4.38" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 191 | dependencies = [ 192 | "android-tzdata", 193 | "iana-time-zone", 194 | "js-sys", 195 | "num-traits", 196 | "wasm-bindgen", 197 | "windows-targets 0.52.6", 198 | ] 199 | 200 | [[package]] 201 | name = "clap" 202 | version = "4.5.16" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" 205 | dependencies = [ 206 | "clap_builder", 207 | "clap_derive", 208 | ] 209 | 210 | [[package]] 211 | name = "clap_builder" 212 | version = "4.5.15" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" 215 | dependencies = [ 216 | "anstream", 217 | "anstyle", 218 | "clap_lex", 219 | "strsim", 220 | ] 221 | 222 | [[package]] 223 | name = "clap_derive" 224 | version = "4.5.13" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" 227 | dependencies = [ 228 | "heck", 229 | "proc-macro2", 230 | "quote", 231 | "syn 2.0.77", 232 | ] 233 | 234 | [[package]] 235 | name = "clap_lex" 236 | version = "0.7.2" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 239 | 240 | [[package]] 241 | name = "colorchoice" 242 | version = "1.0.2" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 245 | 246 | [[package]] 247 | name = "core-foundation" 248 | version = "0.9.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 251 | dependencies = [ 252 | "core-foundation-sys", 253 | "libc", 254 | ] 255 | 256 | [[package]] 257 | name = "core-foundation-sys" 258 | version = "0.8.7" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 261 | 262 | [[package]] 263 | name = "crossbeam-channel" 264 | version = "0.5.13" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 267 | dependencies = [ 268 | "crossbeam-utils", 269 | ] 270 | 271 | [[package]] 272 | name = "crossbeam-utils" 273 | version = "0.8.20" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 276 | 277 | [[package]] 278 | name = "crunchy" 279 | version = "0.2.2" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 282 | 283 | [[package]] 284 | name = "dashmap" 285 | version = "5.5.3" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 288 | dependencies = [ 289 | "cfg-if", 290 | "hashbrown 0.14.5", 291 | "lock_api", 292 | "once_cell", 293 | "parking_lot_core", 294 | ] 295 | 296 | [[package]] 297 | name = "deranged" 298 | version = "0.3.11" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 301 | dependencies = [ 302 | "powerfmt", 303 | ] 304 | 305 | [[package]] 306 | name = "displaydoc" 307 | version = "0.2.5" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 310 | dependencies = [ 311 | "proc-macro2", 312 | "quote", 313 | "syn 2.0.77", 314 | ] 315 | 316 | [[package]] 317 | name = "encoding_rs" 318 | version = "0.8.34" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 321 | dependencies = [ 322 | "cfg-if", 323 | ] 324 | 325 | [[package]] 326 | name = "equivalent" 327 | version = "1.0.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 330 | 331 | [[package]] 332 | name = "errno" 333 | version = "0.3.9" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 336 | dependencies = [ 337 | "libc", 338 | "windows-sys 0.52.0", 339 | ] 340 | 341 | [[package]] 342 | name = "fastrand" 343 | version = "2.1.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 346 | 347 | [[package]] 348 | name = "fluent" 349 | version = "0.16.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" 352 | dependencies = [ 353 | "fluent-bundle", 354 | "unic-langid", 355 | ] 356 | 357 | [[package]] 358 | name = "fluent-bundle" 359 | version = "0.15.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" 362 | dependencies = [ 363 | "fluent-langneg", 364 | "fluent-syntax", 365 | "intl-memoizer", 366 | "intl_pluralrules", 367 | "rustc-hash", 368 | "self_cell 0.10.3", 369 | "smallvec", 370 | "unic-langid", 371 | ] 372 | 373 | [[package]] 374 | name = "fluent-langneg" 375 | version = "0.13.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" 378 | dependencies = [ 379 | "unic-langid", 380 | ] 381 | 382 | [[package]] 383 | name = "fluent-syntax" 384 | version = "0.11.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" 387 | dependencies = [ 388 | "thiserror", 389 | ] 390 | 391 | [[package]] 392 | name = "fnv" 393 | version = "1.0.7" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 396 | 397 | [[package]] 398 | name = "foreign-types" 399 | version = "0.3.2" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 402 | dependencies = [ 403 | "foreign-types-shared", 404 | ] 405 | 406 | [[package]] 407 | name = "foreign-types-shared" 408 | version = "0.1.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 411 | 412 | [[package]] 413 | name = "form_urlencoded" 414 | version = "1.2.1" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 417 | dependencies = [ 418 | "percent-encoding", 419 | ] 420 | 421 | [[package]] 422 | name = "futures-channel" 423 | version = "0.3.30" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 426 | dependencies = [ 427 | "futures-core", 428 | ] 429 | 430 | [[package]] 431 | name = "futures-core" 432 | version = "0.3.30" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 435 | 436 | [[package]] 437 | name = "futures-sink" 438 | version = "0.3.30" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 441 | 442 | [[package]] 443 | name = "futures-task" 444 | version = "0.3.30" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 447 | 448 | [[package]] 449 | name = "futures-util" 450 | version = "0.3.30" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 453 | dependencies = [ 454 | "futures-core", 455 | "futures-task", 456 | "pin-project-lite", 457 | "pin-utils", 458 | ] 459 | 460 | [[package]] 461 | name = "getrandom" 462 | version = "0.2.15" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 465 | dependencies = [ 466 | "cfg-if", 467 | "libc", 468 | "wasi", 469 | ] 470 | 471 | [[package]] 472 | name = "gimli" 473 | version = "0.29.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 476 | 477 | [[package]] 478 | name = "h2" 479 | version = "0.3.26" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 482 | dependencies = [ 483 | "bytes", 484 | "fnv", 485 | "futures-core", 486 | "futures-sink", 487 | "futures-util", 488 | "http", 489 | "indexmap", 490 | "slab", 491 | "tokio", 492 | "tokio-util", 493 | "tracing", 494 | ] 495 | 496 | [[package]] 497 | name = "half" 498 | version = "2.2.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" 501 | dependencies = [ 502 | "crunchy", 503 | ] 504 | 505 | [[package]] 506 | name = "hashbrown" 507 | version = "0.13.2" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 510 | dependencies = [ 511 | "ahash", 512 | ] 513 | 514 | [[package]] 515 | name = "hashbrown" 516 | version = "0.14.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 519 | 520 | [[package]] 521 | name = "heck" 522 | version = "0.5.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 525 | 526 | [[package]] 527 | name = "hermit-abi" 528 | version = "0.3.9" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 531 | 532 | [[package]] 533 | name = "http" 534 | version = "0.2.12" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 537 | dependencies = [ 538 | "bytes", 539 | "fnv", 540 | "itoa", 541 | ] 542 | 543 | [[package]] 544 | name = "http-body" 545 | version = "0.4.6" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 548 | dependencies = [ 549 | "bytes", 550 | "http", 551 | "pin-project-lite", 552 | ] 553 | 554 | [[package]] 555 | name = "httparse" 556 | version = "1.9.4" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 559 | 560 | [[package]] 561 | name = "httpdate" 562 | version = "1.0.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 565 | 566 | [[package]] 567 | name = "hyper" 568 | version = "0.14.30" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" 571 | dependencies = [ 572 | "bytes", 573 | "futures-channel", 574 | "futures-core", 575 | "futures-util", 576 | "h2", 577 | "http", 578 | "http-body", 579 | "httparse", 580 | "httpdate", 581 | "itoa", 582 | "pin-project-lite", 583 | "socket2", 584 | "tokio", 585 | "tower-service", 586 | "tracing", 587 | "want", 588 | ] 589 | 590 | [[package]] 591 | name = "hyper-tls" 592 | version = "0.5.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 595 | dependencies = [ 596 | "bytes", 597 | "hyper", 598 | "native-tls", 599 | "tokio", 600 | "tokio-native-tls", 601 | ] 602 | 603 | [[package]] 604 | name = "iana-time-zone" 605 | version = "0.1.60" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 608 | dependencies = [ 609 | "android_system_properties", 610 | "core-foundation-sys", 611 | "iana-time-zone-haiku", 612 | "js-sys", 613 | "wasm-bindgen", 614 | "windows-core", 615 | ] 616 | 617 | [[package]] 618 | name = "iana-time-zone-haiku" 619 | version = "0.1.2" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 622 | dependencies = [ 623 | "cc", 624 | ] 625 | 626 | [[package]] 627 | name = "idna" 628 | version = "0.5.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 631 | dependencies = [ 632 | "unicode-bidi", 633 | "unicode-normalization", 634 | ] 635 | 636 | [[package]] 637 | name = "indexmap" 638 | version = "2.5.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 641 | dependencies = [ 642 | "equivalent", 643 | "hashbrown 0.14.5", 644 | ] 645 | 646 | [[package]] 647 | name = "intl-memoizer" 648 | version = "0.5.2" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" 651 | dependencies = [ 652 | "type-map", 653 | "unic-langid", 654 | ] 655 | 656 | [[package]] 657 | name = "intl_pluralrules" 658 | version = "7.0.2" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" 661 | dependencies = [ 662 | "unic-langid", 663 | ] 664 | 665 | [[package]] 666 | name = "ipnet" 667 | version = "2.9.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 670 | 671 | [[package]] 672 | name = "is_terminal_polyfill" 673 | version = "1.70.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 676 | 677 | [[package]] 678 | name = "itoa" 679 | version = "1.0.11" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 682 | 683 | [[package]] 684 | name = "js-sys" 685 | version = "0.3.70" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 688 | dependencies = [ 689 | "wasm-bindgen", 690 | ] 691 | 692 | [[package]] 693 | name = "lazy_static" 694 | version = "1.5.0" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 697 | 698 | [[package]] 699 | name = "libc" 700 | version = "0.2.158" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 703 | 704 | [[package]] 705 | name = "linux-raw-sys" 706 | version = "0.4.14" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 709 | 710 | [[package]] 711 | name = "lock_api" 712 | version = "0.4.12" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 715 | dependencies = [ 716 | "autocfg", 717 | "scopeguard", 718 | ] 719 | 720 | [[package]] 721 | name = "log" 722 | version = "0.4.22" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 725 | 726 | [[package]] 727 | name = "lru" 728 | version = "0.10.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" 731 | dependencies = [ 732 | "hashbrown 0.13.2", 733 | ] 734 | 735 | [[package]] 736 | name = "matchers" 737 | version = "0.1.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 740 | dependencies = [ 741 | "regex-automata 0.1.10", 742 | ] 743 | 744 | [[package]] 745 | name = "memchr" 746 | version = "2.7.4" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 749 | 750 | [[package]] 751 | name = "mime" 752 | version = "0.3.17" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 755 | 756 | [[package]] 757 | name = "miniz_oxide" 758 | version = "0.7.4" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 761 | dependencies = [ 762 | "adler", 763 | ] 764 | 765 | [[package]] 766 | name = "mio" 767 | version = "1.0.2" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 770 | dependencies = [ 771 | "hermit-abi", 772 | "libc", 773 | "wasi", 774 | "windows-sys 0.52.0", 775 | ] 776 | 777 | [[package]] 778 | name = "native-tls" 779 | version = "0.2.12" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 782 | dependencies = [ 783 | "libc", 784 | "log", 785 | "openssl", 786 | "openssl-probe", 787 | "openssl-sys", 788 | "schannel", 789 | "security-framework", 790 | "security-framework-sys", 791 | "tempfile", 792 | ] 793 | 794 | [[package]] 795 | name = "nu-ansi-term" 796 | version = "0.46.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 799 | dependencies = [ 800 | "overload", 801 | "winapi", 802 | ] 803 | 804 | [[package]] 805 | name = "num-conv" 806 | version = "0.1.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 809 | 810 | [[package]] 811 | name = "num-traits" 812 | version = "0.2.19" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 815 | dependencies = [ 816 | "autocfg", 817 | ] 818 | 819 | [[package]] 820 | name = "object" 821 | version = "0.36.4" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" 824 | dependencies = [ 825 | "memchr", 826 | ] 827 | 828 | [[package]] 829 | name = "once_cell" 830 | version = "1.19.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 833 | 834 | [[package]] 835 | name = "openssl" 836 | version = "0.10.66" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 839 | dependencies = [ 840 | "bitflags 2.6.0", 841 | "cfg-if", 842 | "foreign-types", 843 | "libc", 844 | "once_cell", 845 | "openssl-macros", 846 | "openssl-sys", 847 | ] 848 | 849 | [[package]] 850 | name = "openssl-macros" 851 | version = "0.1.1" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 854 | dependencies = [ 855 | "proc-macro2", 856 | "quote", 857 | "syn 2.0.77", 858 | ] 859 | 860 | [[package]] 861 | name = "openssl-probe" 862 | version = "0.1.5" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 865 | 866 | [[package]] 867 | name = "openssl-sys" 868 | version = "0.9.103" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 871 | dependencies = [ 872 | "cc", 873 | "libc", 874 | "pkg-config", 875 | "vcpkg", 876 | ] 877 | 878 | [[package]] 879 | name = "overload" 880 | version = "0.1.1" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 883 | 884 | [[package]] 885 | name = "parking_lot_core" 886 | version = "0.9.10" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 889 | dependencies = [ 890 | "cfg-if", 891 | "libc", 892 | "redox_syscall", 893 | "smallvec", 894 | "windows-targets 0.52.6", 895 | ] 896 | 897 | [[package]] 898 | name = "percent-encoding" 899 | version = "2.3.1" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 902 | 903 | [[package]] 904 | name = "phira-mp-client" 905 | version = "0.1.0" 906 | dependencies = [ 907 | "anyhow", 908 | "chrono", 909 | "dashmap", 910 | "phira-mp-common", 911 | "tokio", 912 | "tracing", 913 | "uuid", 914 | ] 915 | 916 | [[package]] 917 | name = "phira-mp-common" 918 | version = "0.1.0" 919 | dependencies = [ 920 | "anyhow", 921 | "byteorder", 922 | "chrono", 923 | "half", 924 | "phira-mp-macros", 925 | "tap", 926 | "tokio", 927 | "tracing", 928 | "uuid", 929 | ] 930 | 931 | [[package]] 932 | name = "phira-mp-macros" 933 | version = "0.1.0" 934 | dependencies = [ 935 | "proc-macro2", 936 | "quote", 937 | "syn 1.0.109", 938 | ] 939 | 940 | [[package]] 941 | name = "phira-mp-server" 942 | version = "0.1.0" 943 | dependencies = [ 944 | "anyhow", 945 | "clap", 946 | "fluent", 947 | "fluent-syntax", 948 | "intl-memoizer", 949 | "lru", 950 | "once_cell", 951 | "phira-mp-common", 952 | "rand", 953 | "reqwest", 954 | "serde", 955 | "serde_yaml", 956 | "tap", 957 | "tokio", 958 | "tracing", 959 | "tracing-appender", 960 | "tracing-log 0.1.4", 961 | "tracing-subscriber", 962 | "unic-langid", 963 | "uuid", 964 | ] 965 | 966 | [[package]] 967 | name = "pin-project-lite" 968 | version = "0.2.14" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 971 | 972 | [[package]] 973 | name = "pin-utils" 974 | version = "0.1.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 977 | 978 | [[package]] 979 | name = "pkg-config" 980 | version = "0.3.30" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 983 | 984 | [[package]] 985 | name = "powerfmt" 986 | version = "0.2.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 989 | 990 | [[package]] 991 | name = "ppv-lite86" 992 | version = "0.2.20" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 995 | dependencies = [ 996 | "zerocopy", 997 | ] 998 | 999 | [[package]] 1000 | name = "proc-macro-hack" 1001 | version = "0.5.20+deprecated" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 1004 | 1005 | [[package]] 1006 | name = "proc-macro2" 1007 | version = "1.0.86" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1010 | dependencies = [ 1011 | "unicode-ident", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "quote" 1016 | version = "1.0.37" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1019 | dependencies = [ 1020 | "proc-macro2", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "rand" 1025 | version = "0.8.5" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1028 | dependencies = [ 1029 | "libc", 1030 | "rand_chacha", 1031 | "rand_core", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "rand_chacha" 1036 | version = "0.3.1" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1039 | dependencies = [ 1040 | "ppv-lite86", 1041 | "rand_core", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "rand_core" 1046 | version = "0.6.4" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1049 | dependencies = [ 1050 | "getrandom", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "redox_syscall" 1055 | version = "0.5.3" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 1058 | dependencies = [ 1059 | "bitflags 2.6.0", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "regex" 1064 | version = "1.10.6" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 1067 | dependencies = [ 1068 | "aho-corasick", 1069 | "memchr", 1070 | "regex-automata 0.4.7", 1071 | "regex-syntax 0.8.4", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "regex-automata" 1076 | version = "0.1.10" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1079 | dependencies = [ 1080 | "regex-syntax 0.6.29", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "regex-automata" 1085 | version = "0.4.7" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1088 | dependencies = [ 1089 | "aho-corasick", 1090 | "memchr", 1091 | "regex-syntax 0.8.4", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "regex-syntax" 1096 | version = "0.6.29" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1099 | 1100 | [[package]] 1101 | name = "regex-syntax" 1102 | version = "0.8.4" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1105 | 1106 | [[package]] 1107 | name = "reqwest" 1108 | version = "0.11.27" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1111 | dependencies = [ 1112 | "base64", 1113 | "bytes", 1114 | "encoding_rs", 1115 | "futures-core", 1116 | "futures-util", 1117 | "h2", 1118 | "http", 1119 | "http-body", 1120 | "hyper", 1121 | "hyper-tls", 1122 | "ipnet", 1123 | "js-sys", 1124 | "log", 1125 | "mime", 1126 | "native-tls", 1127 | "once_cell", 1128 | "percent-encoding", 1129 | "pin-project-lite", 1130 | "rustls-pemfile", 1131 | "serde", 1132 | "serde_json", 1133 | "serde_urlencoded", 1134 | "sync_wrapper", 1135 | "system-configuration", 1136 | "tokio", 1137 | "tokio-native-tls", 1138 | "tower-service", 1139 | "url", 1140 | "wasm-bindgen", 1141 | "wasm-bindgen-futures", 1142 | "web-sys", 1143 | "winreg", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "rustc-demangle" 1148 | version = "0.1.24" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1151 | 1152 | [[package]] 1153 | name = "rustc-hash" 1154 | version = "1.1.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1157 | 1158 | [[package]] 1159 | name = "rustix" 1160 | version = "0.38.35" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" 1163 | dependencies = [ 1164 | "bitflags 2.6.0", 1165 | "errno", 1166 | "libc", 1167 | "linux-raw-sys", 1168 | "windows-sys 0.52.0", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "rustls-pemfile" 1173 | version = "1.0.4" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1176 | dependencies = [ 1177 | "base64", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "ryu" 1182 | version = "1.0.18" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1185 | 1186 | [[package]] 1187 | name = "schannel" 1188 | version = "0.1.23" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1191 | dependencies = [ 1192 | "windows-sys 0.52.0", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "scopeguard" 1197 | version = "1.2.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1200 | 1201 | [[package]] 1202 | name = "security-framework" 1203 | version = "2.11.1" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1206 | dependencies = [ 1207 | "bitflags 2.6.0", 1208 | "core-foundation", 1209 | "core-foundation-sys", 1210 | "libc", 1211 | "security-framework-sys", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "security-framework-sys" 1216 | version = "2.11.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" 1219 | dependencies = [ 1220 | "core-foundation-sys", 1221 | "libc", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "self_cell" 1226 | version = "0.10.3" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" 1229 | dependencies = [ 1230 | "self_cell 1.0.4", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "self_cell" 1235 | version = "1.0.4" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" 1238 | 1239 | [[package]] 1240 | name = "serde" 1241 | version = "1.0.209" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 1244 | dependencies = [ 1245 | "serde_derive", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "serde_derive" 1250 | version = "1.0.209" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 1253 | dependencies = [ 1254 | "proc-macro2", 1255 | "quote", 1256 | "syn 2.0.77", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "serde_json" 1261 | version = "1.0.127" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" 1264 | dependencies = [ 1265 | "itoa", 1266 | "memchr", 1267 | "ryu", 1268 | "serde", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "serde_urlencoded" 1273 | version = "0.7.1" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1276 | dependencies = [ 1277 | "form_urlencoded", 1278 | "itoa", 1279 | "ryu", 1280 | "serde", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "serde_yaml" 1285 | version = "0.9.34+deprecated" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1288 | dependencies = [ 1289 | "indexmap", 1290 | "itoa", 1291 | "ryu", 1292 | "serde", 1293 | "unsafe-libyaml", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "sharded-slab" 1298 | version = "0.1.7" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1301 | dependencies = [ 1302 | "lazy_static", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "shlex" 1307 | version = "1.3.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1310 | 1311 | [[package]] 1312 | name = "slab" 1313 | version = "0.4.9" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1316 | dependencies = [ 1317 | "autocfg", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "smallvec" 1322 | version = "1.13.2" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1325 | 1326 | [[package]] 1327 | name = "socket2" 1328 | version = "0.5.7" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1331 | dependencies = [ 1332 | "libc", 1333 | "windows-sys 0.52.0", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "strsim" 1338 | version = "0.11.1" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1341 | 1342 | [[package]] 1343 | name = "syn" 1344 | version = "1.0.109" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1347 | dependencies = [ 1348 | "proc-macro2", 1349 | "quote", 1350 | "unicode-ident", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "syn" 1355 | version = "2.0.77" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 1358 | dependencies = [ 1359 | "proc-macro2", 1360 | "quote", 1361 | "unicode-ident", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "sync_wrapper" 1366 | version = "0.1.2" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1369 | 1370 | [[package]] 1371 | name = "system-configuration" 1372 | version = "0.5.1" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1375 | dependencies = [ 1376 | "bitflags 1.3.2", 1377 | "core-foundation", 1378 | "system-configuration-sys", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "system-configuration-sys" 1383 | version = "0.5.0" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1386 | dependencies = [ 1387 | "core-foundation-sys", 1388 | "libc", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "tap" 1393 | version = "1.0.1" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1396 | 1397 | [[package]] 1398 | name = "tempfile" 1399 | version = "3.12.0" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 1402 | dependencies = [ 1403 | "cfg-if", 1404 | "fastrand", 1405 | "once_cell", 1406 | "rustix", 1407 | "windows-sys 0.59.0", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "thiserror" 1412 | version = "1.0.63" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 1415 | dependencies = [ 1416 | "thiserror-impl", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "thiserror-impl" 1421 | version = "1.0.63" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1424 | dependencies = [ 1425 | "proc-macro2", 1426 | "quote", 1427 | "syn 2.0.77", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "thread_local" 1432 | version = "1.1.8" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1435 | dependencies = [ 1436 | "cfg-if", 1437 | "once_cell", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "time" 1442 | version = "0.3.36" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1445 | dependencies = [ 1446 | "deranged", 1447 | "itoa", 1448 | "num-conv", 1449 | "powerfmt", 1450 | "serde", 1451 | "time-core", 1452 | "time-macros", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "time-core" 1457 | version = "0.1.2" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1460 | 1461 | [[package]] 1462 | name = "time-macros" 1463 | version = "0.2.18" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1466 | dependencies = [ 1467 | "num-conv", 1468 | "time-core", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "tinystr" 1473 | version = "0.7.6" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1476 | dependencies = [ 1477 | "displaydoc", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "tinyvec" 1482 | version = "1.8.0" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1485 | dependencies = [ 1486 | "tinyvec_macros", 1487 | ] 1488 | 1489 | [[package]] 1490 | name = "tinyvec_macros" 1491 | version = "0.1.1" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1494 | 1495 | [[package]] 1496 | name = "tokio" 1497 | version = "1.40.0" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 1500 | dependencies = [ 1501 | "backtrace", 1502 | "bytes", 1503 | "libc", 1504 | "mio", 1505 | "pin-project-lite", 1506 | "socket2", 1507 | "tokio-macros", 1508 | "windows-sys 0.52.0", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "tokio-macros" 1513 | version = "2.4.0" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1516 | dependencies = [ 1517 | "proc-macro2", 1518 | "quote", 1519 | "syn 2.0.77", 1520 | ] 1521 | 1522 | [[package]] 1523 | name = "tokio-native-tls" 1524 | version = "0.3.1" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1527 | dependencies = [ 1528 | "native-tls", 1529 | "tokio", 1530 | ] 1531 | 1532 | [[package]] 1533 | name = "tokio-util" 1534 | version = "0.7.11" 1535 | source = "registry+https://github.com/rust-lang/crates.io-index" 1536 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 1537 | dependencies = [ 1538 | "bytes", 1539 | "futures-core", 1540 | "futures-sink", 1541 | "pin-project-lite", 1542 | "tokio", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "tower-service" 1547 | version = "0.3.3" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1550 | 1551 | [[package]] 1552 | name = "tracing" 1553 | version = "0.1.40" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1556 | dependencies = [ 1557 | "pin-project-lite", 1558 | "tracing-attributes", 1559 | "tracing-core", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "tracing-appender" 1564 | version = "0.2.3" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" 1567 | dependencies = [ 1568 | "crossbeam-channel", 1569 | "thiserror", 1570 | "time", 1571 | "tracing-subscriber", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "tracing-attributes" 1576 | version = "0.1.27" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1579 | dependencies = [ 1580 | "proc-macro2", 1581 | "quote", 1582 | "syn 2.0.77", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "tracing-core" 1587 | version = "0.1.32" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1590 | dependencies = [ 1591 | "once_cell", 1592 | "valuable", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "tracing-log" 1597 | version = "0.1.4" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" 1600 | dependencies = [ 1601 | "log", 1602 | "once_cell", 1603 | "tracing-core", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "tracing-log" 1608 | version = "0.2.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1611 | dependencies = [ 1612 | "log", 1613 | "once_cell", 1614 | "tracing-core", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "tracing-subscriber" 1619 | version = "0.3.18" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1622 | dependencies = [ 1623 | "matchers", 1624 | "nu-ansi-term", 1625 | "once_cell", 1626 | "regex", 1627 | "sharded-slab", 1628 | "smallvec", 1629 | "thread_local", 1630 | "tracing", 1631 | "tracing-core", 1632 | "tracing-log 0.2.0", 1633 | ] 1634 | 1635 | [[package]] 1636 | name = "try-lock" 1637 | version = "0.2.5" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1640 | 1641 | [[package]] 1642 | name = "type-map" 1643 | version = "0.5.0" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" 1646 | dependencies = [ 1647 | "rustc-hash", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "unic-langid" 1652 | version = "0.9.5" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" 1655 | dependencies = [ 1656 | "unic-langid-impl", 1657 | "unic-langid-macros", 1658 | ] 1659 | 1660 | [[package]] 1661 | name = "unic-langid-impl" 1662 | version = "0.9.5" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" 1665 | dependencies = [ 1666 | "tinystr", 1667 | ] 1668 | 1669 | [[package]] 1670 | name = "unic-langid-macros" 1671 | version = "0.9.5" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "0da1cd2c042d3c7569a1008806b02039e7a4a2bdf8f8e96bd3c792434a0e275e" 1674 | dependencies = [ 1675 | "proc-macro-hack", 1676 | "tinystr", 1677 | "unic-langid-impl", 1678 | "unic-langid-macros-impl", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "unic-langid-macros-impl" 1683 | version = "0.9.5" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "1ed7f4237ba393424195053097c1516bd4590dc82b84f2f97c5c69e12704555b" 1686 | dependencies = [ 1687 | "proc-macro-hack", 1688 | "quote", 1689 | "syn 2.0.77", 1690 | "unic-langid-impl", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "unicode-bidi" 1695 | version = "0.3.15" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1698 | 1699 | [[package]] 1700 | name = "unicode-ident" 1701 | version = "1.0.12" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1704 | 1705 | [[package]] 1706 | name = "unicode-normalization" 1707 | version = "0.1.23" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1710 | dependencies = [ 1711 | "tinyvec", 1712 | ] 1713 | 1714 | [[package]] 1715 | name = "unsafe-libyaml" 1716 | version = "0.2.11" 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" 1718 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1719 | 1720 | [[package]] 1721 | name = "url" 1722 | version = "2.5.2" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1725 | dependencies = [ 1726 | "form_urlencoded", 1727 | "idna", 1728 | "percent-encoding", 1729 | ] 1730 | 1731 | [[package]] 1732 | name = "utf8parse" 1733 | version = "0.2.2" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1736 | 1737 | [[package]] 1738 | name = "uuid" 1739 | version = "1.10.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" 1742 | dependencies = [ 1743 | "getrandom", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "valuable" 1748 | version = "0.1.0" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1751 | 1752 | [[package]] 1753 | name = "vcpkg" 1754 | version = "0.2.15" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1757 | 1758 | [[package]] 1759 | name = "version_check" 1760 | version = "0.9.5" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1763 | 1764 | [[package]] 1765 | name = "want" 1766 | version = "0.3.1" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1769 | dependencies = [ 1770 | "try-lock", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "wasi" 1775 | version = "0.11.0+wasi-snapshot-preview1" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1778 | 1779 | [[package]] 1780 | name = "wasm-bindgen" 1781 | version = "0.2.93" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1784 | dependencies = [ 1785 | "cfg-if", 1786 | "once_cell", 1787 | "wasm-bindgen-macro", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "wasm-bindgen-backend" 1792 | version = "0.2.93" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1795 | dependencies = [ 1796 | "bumpalo", 1797 | "log", 1798 | "once_cell", 1799 | "proc-macro2", 1800 | "quote", 1801 | "syn 2.0.77", 1802 | "wasm-bindgen-shared", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "wasm-bindgen-futures" 1807 | version = "0.4.43" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 1810 | dependencies = [ 1811 | "cfg-if", 1812 | "js-sys", 1813 | "wasm-bindgen", 1814 | "web-sys", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "wasm-bindgen-macro" 1819 | version = "0.2.93" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1822 | dependencies = [ 1823 | "quote", 1824 | "wasm-bindgen-macro-support", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "wasm-bindgen-macro-support" 1829 | version = "0.2.93" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1832 | dependencies = [ 1833 | "proc-macro2", 1834 | "quote", 1835 | "syn 2.0.77", 1836 | "wasm-bindgen-backend", 1837 | "wasm-bindgen-shared", 1838 | ] 1839 | 1840 | [[package]] 1841 | name = "wasm-bindgen-shared" 1842 | version = "0.2.93" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1845 | 1846 | [[package]] 1847 | name = "web-sys" 1848 | version = "0.3.70" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1851 | dependencies = [ 1852 | "js-sys", 1853 | "wasm-bindgen", 1854 | ] 1855 | 1856 | [[package]] 1857 | name = "winapi" 1858 | version = "0.3.9" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1861 | dependencies = [ 1862 | "winapi-i686-pc-windows-gnu", 1863 | "winapi-x86_64-pc-windows-gnu", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "winapi-i686-pc-windows-gnu" 1868 | version = "0.4.0" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1871 | 1872 | [[package]] 1873 | name = "winapi-x86_64-pc-windows-gnu" 1874 | version = "0.4.0" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1877 | 1878 | [[package]] 1879 | name = "windows-core" 1880 | version = "0.52.0" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1883 | dependencies = [ 1884 | "windows-targets 0.52.6", 1885 | ] 1886 | 1887 | [[package]] 1888 | name = "windows-sys" 1889 | version = "0.48.0" 1890 | source = "registry+https://github.com/rust-lang/crates.io-index" 1891 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1892 | dependencies = [ 1893 | "windows-targets 0.48.5", 1894 | ] 1895 | 1896 | [[package]] 1897 | name = "windows-sys" 1898 | version = "0.52.0" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1901 | dependencies = [ 1902 | "windows-targets 0.52.6", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "windows-sys" 1907 | version = "0.59.0" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1910 | dependencies = [ 1911 | "windows-targets 0.52.6", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "windows-targets" 1916 | version = "0.48.5" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1919 | dependencies = [ 1920 | "windows_aarch64_gnullvm 0.48.5", 1921 | "windows_aarch64_msvc 0.48.5", 1922 | "windows_i686_gnu 0.48.5", 1923 | "windows_i686_msvc 0.48.5", 1924 | "windows_x86_64_gnu 0.48.5", 1925 | "windows_x86_64_gnullvm 0.48.5", 1926 | "windows_x86_64_msvc 0.48.5", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "windows-targets" 1931 | version = "0.52.6" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1934 | dependencies = [ 1935 | "windows_aarch64_gnullvm 0.52.6", 1936 | "windows_aarch64_msvc 0.52.6", 1937 | "windows_i686_gnu 0.52.6", 1938 | "windows_i686_gnullvm", 1939 | "windows_i686_msvc 0.52.6", 1940 | "windows_x86_64_gnu 0.52.6", 1941 | "windows_x86_64_gnullvm 0.52.6", 1942 | "windows_x86_64_msvc 0.52.6", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "windows_aarch64_gnullvm" 1947 | version = "0.48.5" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1950 | 1951 | [[package]] 1952 | name = "windows_aarch64_gnullvm" 1953 | version = "0.52.6" 1954 | source = "registry+https://github.com/rust-lang/crates.io-index" 1955 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1956 | 1957 | [[package]] 1958 | name = "windows_aarch64_msvc" 1959 | version = "0.48.5" 1960 | source = "registry+https://github.com/rust-lang/crates.io-index" 1961 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1962 | 1963 | [[package]] 1964 | name = "windows_aarch64_msvc" 1965 | version = "0.52.6" 1966 | source = "registry+https://github.com/rust-lang/crates.io-index" 1967 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1968 | 1969 | [[package]] 1970 | name = "windows_i686_gnu" 1971 | version = "0.48.5" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1974 | 1975 | [[package]] 1976 | name = "windows_i686_gnu" 1977 | version = "0.52.6" 1978 | source = "registry+https://github.com/rust-lang/crates.io-index" 1979 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1980 | 1981 | [[package]] 1982 | name = "windows_i686_gnullvm" 1983 | version = "0.52.6" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1986 | 1987 | [[package]] 1988 | name = "windows_i686_msvc" 1989 | version = "0.48.5" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1992 | 1993 | [[package]] 1994 | name = "windows_i686_msvc" 1995 | version = "0.52.6" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1998 | 1999 | [[package]] 2000 | name = "windows_x86_64_gnu" 2001 | version = "0.48.5" 2002 | source = "registry+https://github.com/rust-lang/crates.io-index" 2003 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2004 | 2005 | [[package]] 2006 | name = "windows_x86_64_gnu" 2007 | version = "0.52.6" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2010 | 2011 | [[package]] 2012 | name = "windows_x86_64_gnullvm" 2013 | version = "0.48.5" 2014 | source = "registry+https://github.com/rust-lang/crates.io-index" 2015 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2016 | 2017 | [[package]] 2018 | name = "windows_x86_64_gnullvm" 2019 | version = "0.52.6" 2020 | source = "registry+https://github.com/rust-lang/crates.io-index" 2021 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2022 | 2023 | [[package]] 2024 | name = "windows_x86_64_msvc" 2025 | version = "0.48.5" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2028 | 2029 | [[package]] 2030 | name = "windows_x86_64_msvc" 2031 | version = "0.52.6" 2032 | source = "registry+https://github.com/rust-lang/crates.io-index" 2033 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2034 | 2035 | [[package]] 2036 | name = "winreg" 2037 | version = "0.50.0" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2040 | dependencies = [ 2041 | "cfg-if", 2042 | "windows-sys 0.48.0", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "zerocopy" 2047 | version = "0.7.35" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2050 | dependencies = [ 2051 | "byteorder", 2052 | "zerocopy-derive", 2053 | ] 2054 | 2055 | [[package]] 2056 | name = "zerocopy-derive" 2057 | version = "0.7.35" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2060 | dependencies = [ 2061 | "proc-macro2", 2062 | "quote", 2063 | "syn 2.0.77", 2064 | ] 2065 | --------------------------------------------------------------------------------