Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 251 additions & 1 deletion phira-mp-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use phira_mp_common::{
JudgeEvent, Message, RoomId, RoomState, ServerCommand, Stream, TouchFrame, UserInfo,
};
use std::{
collections::VecDeque,
sync::{
Arc,
atomic::{AtomicU8, Ordering},
Expand All @@ -25,6 +26,31 @@ type Callback<T> = Mutex<Option<oneshot::Sender<T>>>;
type RCallback<T, E = String> = Mutex<Option<oneshot::Sender<Result<T, E>>>>;

pub const TIMEOUT: Duration = Duration::from_secs(7);
/// 本地谱面上传/下载等大数据传输的超时(经 game 连接传整个 zip,需更长)。
pub const TRANSFER_TIMEOUT: Duration = Duration::from_secs(120);

/// 本地谱面同步(LocalChart)触发的事件,由服务端主动推送,上层通过
/// [`Client::blocking_take_local_chart_events`] 轮询消费。
#[derive(Clone, Debug)]
pub enum LocalChartEvent {
/// 房间进入 / 退出本地谱面分享状态
ChangeLocalChart { local: bool, chart_id: String },
/// 房主:服务端要求启动本地下载服务器
StartServing { chart_id: String, chart_name: String },
/// 玩家:服务端指示从房主下载谱面
StartDownload {
host_id: i32,
host_name: String,
addr: String,
port: u16,
chart_id: String,
chart_name: String,
},
/// 房主:所有玩家都已完成下载
HostReady,
/// 房主取消了本地谱面分享:所有客户端应重置就绪/开始按钮状态
Canceled,
}

pub struct LivePlayer {
pub touch_frames: Mutex<Vec<TouchFrame>>,
Expand Down Expand Up @@ -67,6 +93,18 @@ struct State {
cb_played: RCallback<()>,
cb_abort: RCallback<()>,

cb_select_local_chart: RCallback<()>,
cb_select_online_chart: RCallback<()>,
cb_send_chart: RCallback<()>,
cb_download_ready: RCallback<()>,
cb_cancel_local_chart: RCallback<()>,
cb_cancel_download_ready: RCallback<()>,

cb_upload_chart: RCallback<()>,
cb_download_chart: RCallback<Vec<u8>>,

local_chart_events: Mutex<VecDeque<LocalChartEvent>>,

live_players: DashMap<i32, Arc<LivePlayer>>,
messages: Mutex<Vec<Message>>,
}
Expand Down Expand Up @@ -116,13 +154,26 @@ impl Client {
cb_played: Callback::default(),
cb_abort: Callback::default(),

cb_select_local_chart: Callback::default(),
cb_select_online_chart: Callback::default(),
cb_send_chart: Callback::default(),
cb_download_ready: Callback::default(),
cb_cancel_local_chart: Callback::default(),
cb_cancel_download_ready: Callback::default(),

cb_upload_chart: Callback::default(),
cb_download_chart: Callback::default(),

local_chart_events: Mutex::default(),

live_players: DashMap::new(),
messages: Mutex::default(),
});
let stream = Arc::new(
Stream::new(
Some(1),
stream,
None,
Box::new({
let state = Arc::clone(&state);
move |_send_tx, cmd| process(Arc::clone(&state), cmd)
Expand Down Expand Up @@ -248,10 +299,19 @@ impl Client {
}

async fn rcall<R>(&self, payload: ClientCommand, cb: &RCallback<R>) -> Result<R> {
self.rcall_with_timeout(payload, cb, TIMEOUT).await
}

async fn rcall_with_timeout<R>(
&self,
payload: ClientCommand,
cb: &RCallback<R>,
timeout: Duration,
) -> Result<R> {
self.stream.send(payload).await?;
let (tx, rx) = oneshot::channel();
*cb.lock().await = Some(tx);
time::timeout(TIMEOUT, rx)
time::timeout(timeout, rx)
.await
.context("timeout")??
.map_err(Error::msg)
Expand Down Expand Up @@ -384,12 +444,128 @@ impl Client {
Ok(())
}

#[inline]
pub async fn select_local_chart(&self, id: impl Into<String>, name: impl Into<String>) -> Result<()> {
self.rcall(
ClientCommand::SelectLocalChart {
id: id.into().try_into()?,
name: name.into().try_into()?,
},
&self.state.cb_select_local_chart,
)
.await
}

#[inline]
pub async fn select_online_chart(&self, id: i32) -> Result<()> {
self.rcall(
ClientCommand::SelectOnlineChart { id },
&self.state.cb_select_online_chart,
)
.await
}

#[inline]
pub async fn send_chart(&self, addr: impl Into<String>, port: u16) -> Result<()> {
self.rcall(
ClientCommand::SendChart {
addr: addr.into(),
port,
},
&self.state.cb_send_chart,
)
.await
}

#[inline]
pub async fn download_ready(&self) -> Result<()> {
self.rcall(ClientCommand::DownloadReady, &self.state.cb_download_ready)
.await
}

/// 房主取消本地谱面分享(删除服务端缓存、重置所有玩家就绪)
#[inline]
pub async fn cancel_local_chart(&self) -> Result<()> {
self.rcall(ClientCommand::CancelLocalChart, &self.state.cb_cancel_local_chart)
.await
}

/// 玩家取消已就绪(尚未开始游玩前可取消)
#[inline]
pub async fn cancel_download_ready(&self) -> Result<()> {
self.rcall(ClientCommand::CancelDownloadReady, &self.state.cb_cancel_download_ready)
.await
}

/// 上传本地谱面包到服务端(经 game 连接,兼容内网穿透)
#[inline]
pub async fn upload_chart(&self, id: impl Into<String>, data: Vec<u8>) -> Result<()> {
self.rcall_with_timeout(
ClientCommand::UploadChart {
id: id.into().try_into()?,
data,
},
&self.state.cb_upload_chart,
TRANSFER_TIMEOUT,
)
.await
}

/// 从服务端获取谱面包(经 game 连接)
#[inline]
pub async fn download_chart(&self, id: impl Into<String>) -> Result<Vec<u8>> {
self.rcall_with_timeout(
ClientCommand::DownloadChart {
id: id.into().try_into()?,
},
&self.state.cb_download_chart,
TRANSFER_TIMEOUT,
)
.await
}

/// 取走所有尚未被上层消费的本地谱面同步事件
pub fn blocking_take_local_chart_events(&self) -> Vec<LocalChartEvent> {
self.state.local_chart_events.blocking_lock().drain(..).collect()
}

#[inline]
pub async fn played(&self, id: i32) -> Result<()> {
self.rcall(ClientCommand::Played { id }, &self.state.cb_played)
.await
}

/// 本地谱面成绩直传(无官方 record id,客户端上报真实成绩)
#[inline]
pub async fn played_with_score(
&self,
id: i32,
score: u32,
accuracy: f32,
full_combo: bool,
max_combo: u32,
perfect: u32,
good: u32,
bad: u32,
miss: u32,
) -> Result<()> {
self.rcall(
ClientCommand::PlayedWithScore {
id,
score,
accuracy,
full_combo,
max_combo,
perfect,
good,
bad,
miss,
},
&self.state.cb_played,
)
.await
}

#[inline]
pub async fn abort(&self) -> Result<()> {
self.rcall(ClientCommand::Abort, &self.state.cb_abort).await
Expand Down Expand Up @@ -521,5 +697,79 @@ async fn process(state: Arc<State>, cmd: ServerCommand) {
ServerCommand::Abort(res) => {
cb(&state.cb_abort, res).await;
}

ServerCommand::ChangeLocalChart { local, chart_id } => {
state
.local_chart_events
.lock()
.await
.push_back(LocalChartEvent::ChangeLocalChart { local, chart_id });
}
ServerCommand::StartServing { chart_id, chart_name } => {
state
.local_chart_events
.lock()
.await
.push_back(LocalChartEvent::StartServing { chart_id, chart_name });
}
ServerCommand::StartDownload {
host_id,
host_name,
addr,
port,
chart_id,
chart_name,
} => {
state
.local_chart_events
.lock()
.await
.push_back(LocalChartEvent::StartDownload {
host_id,
host_name,
addr,
port,
chart_id,
chart_name,
});
}
ServerCommand::HostReady => {
state
.local_chart_events
.lock()
.await
.push_back(LocalChartEvent::HostReady);
}
ServerCommand::LocalChartCanceled => {
state
.local_chart_events
.lock()
.await
.push_back(LocalChartEvent::Canceled);
}
ServerCommand::SelectLocalChart(res) => {
cb(&state.cb_select_local_chart, res).await;
}
ServerCommand::SelectOnlineChart(res) => {
cb(&state.cb_select_online_chart, res).await;
}
ServerCommand::SendChart(res) => {
cb(&state.cb_send_chart, res).await;
}
ServerCommand::DownloadReady(res) => {
cb(&state.cb_download_ready, res).await;
}
ServerCommand::CancelLocalChart(res) => {
cb(&state.cb_cancel_local_chart, res).await;
}
ServerCommand::CancelDownloadReady(res) => {
cb(&state.cb_cancel_download_ready, res).await;
}
ServerCommand::UploadChart(res) => {
cb(&state.cb_upload_chart, res).await;
}
ServerCommand::DownloadChart(res) => {
cb(&state.cb_download_chart, res).await;
}
}
}
Loading