netplay dispatcher (#532)
* netplay dispatcher * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more
This commit is contained in:
parent
a2d23c2f41
commit
73354246f3
@ -19,7 +19,7 @@ Keys are mapped according to [these defaults](https://github.com/gopher64/gopher
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion or contact me on Discord). You can also run the server (https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the server (https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
|
||||
## portable mode
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ export { NetplayCreate } from "netplay_create.slint";
|
||||
export { NetplayJoin } from "netplay_join.slint";
|
||||
export { NetplayWait } from "netplay_wait.slint";
|
||||
export { CustomNetplayServer } from "netplay_page.slint";
|
||||
export { DispatcherDialog } from "netplay_create.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
callback wiki_button_clicked;
|
||||
|
||||
@ -6,7 +6,6 @@ export component NetplayCreate inherits Window {
|
||||
in-out property server_names <=> NetplayData.server_names;
|
||||
in-out property server_urls <=> NetplayData.server_urls;
|
||||
in-out property custom_server_url <=> NetplayData.custom_server_url;
|
||||
in-out property ping <=> NetplayData.ping;
|
||||
in-out property game_name <=> NetplayData.game_name;
|
||||
in-out property game_hash <=> NetplayData.game_hash;
|
||||
in-out property game_cheats <=> NetplayData.game_cheats;
|
||||
@ -14,7 +13,6 @@ export component NetplayCreate inherits Window {
|
||||
in-out property rom_path <=> NetplayData.rom_path;
|
||||
in-out property peer_addr <=> NetplayData.peer_addr;
|
||||
in-out property rom_dir <=> NetplayData.rom_dir;
|
||||
callback get_ping(string);
|
||||
callback get_custom_url();
|
||||
callback select_rom(string);
|
||||
callback create_session(string, string, string, string, string, string, string);
|
||||
@ -73,17 +71,10 @@ export component NetplayCreate inherits Window {
|
||||
if (self.current-index >= 0) {
|
||||
if (self.current-value == "Custom") {
|
||||
get_custom_url();
|
||||
} else {
|
||||
get_ping(NetplayData.server_urls[self.current-index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
vertical-alignment: center;
|
||||
text: NetplayData.ping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,3 +121,10 @@ export component NetplayCreate inherits Window {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component DispatcherDialog inherits Dialog {
|
||||
title: @tr("Creating Server");
|
||||
Text {
|
||||
text: "Creating server, please wait...This may take about 30 seconds.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,22 +6,21 @@ export component NetplayJoin inherits Window {
|
||||
in-out property server_names <=> NetplayData.server_names;
|
||||
in-out property server_urls <=> NetplayData.server_urls;
|
||||
in-out property custom_server_url <=> NetplayData.custom_server_url;
|
||||
in-out property ping <=> NetplayData.ping;
|
||||
in-out property sessions <=> NetplayData.sessions;
|
||||
in-out property current_session <=> NetplayData.current_session;
|
||||
in-out property ports <=> NetplayData.ports;
|
||||
in-out property room_urls <=> NetplayData.room_urls;
|
||||
in-out property game_name <=> NetplayData.game_name;
|
||||
in-out property game_hash <=> NetplayData.game_hash;
|
||||
in-out property game_cheats <=> NetplayData.game_cheats;
|
||||
in-out property peer_addr <=> NetplayData.peer_addr;
|
||||
in-out property pending_session <=> NetplayData.pending_session;
|
||||
in-out property pending_refresh <=> NetplayData.pending_refresh;
|
||||
in-out property rom_path <=> NetplayData.rom_path;
|
||||
in-out property rom_dir <=> NetplayData.rom_dir;
|
||||
callback get_ping(string);
|
||||
callback get_custom_url();
|
||||
callback refresh_session(string);
|
||||
callback refresh_session();
|
||||
callback select_rom(string);
|
||||
callback join_session(string, string, string, int);
|
||||
callback join_session(string, string, string, string, string);
|
||||
preferred-width: 700px;
|
||||
|
||||
VerticalBox {
|
||||
HorizontalBox {
|
||||
@ -40,26 +39,14 @@ export component NetplayJoin inherits Window {
|
||||
|
||||
VerticalBox {
|
||||
Text {
|
||||
text: @tr("Server:");
|
||||
text: @tr("Custom Server URL (optional):");
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
server := ComboBox {
|
||||
model: NetplayData.server_names;
|
||||
selected => {
|
||||
if (self.current-index >= 0) {
|
||||
if (self.current-value == "Custom") {
|
||||
get_custom_url();
|
||||
} else {
|
||||
get_ping(NetplayData.server_urls[self.current-index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
vertical-alignment: center;
|
||||
text: NetplayData.ping;
|
||||
LineEdit {
|
||||
placeholder-text: @tr("example.com:45000");
|
||||
text: NetplayData.custom_server_url;
|
||||
edited(text) => {
|
||||
NetplayData.custom_server_url = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,12 +58,13 @@ export component NetplayJoin inherits Window {
|
||||
vertical-alignment: center;
|
||||
}
|
||||
if NetplayData.sessions.length == 0: Text {
|
||||
text: @tr("No sessions available. Please refresh or try a different server.");
|
||||
text: @tr("No sessions available. Please refresh to check again.");
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
StandardTableView {
|
||||
columns: [
|
||||
{ title: "Server" },
|
||||
{ title: "Session Name" },
|
||||
{ title: "Game Name" },
|
||||
{ title: "Password" },
|
||||
@ -93,13 +81,14 @@ export component NetplayJoin inherits Window {
|
||||
Rectangle { }
|
||||
|
||||
Button {
|
||||
enabled: !NetplayData.pending_refresh;
|
||||
text: @tr("Refresh Session List");
|
||||
clicked => {
|
||||
if (server.current-value == "Custom") {
|
||||
refresh_session("ws://" + NetplayData.custom_server_url);
|
||||
} else {
|
||||
refresh_session(NetplayData.server_urls[server.current-index]);
|
||||
}
|
||||
NetplayData.pending_refresh = true;
|
||||
NetplayData.current_session = -1;
|
||||
NetplayData.sessions = [];
|
||||
NetplayData.room_urls = [];
|
||||
refresh_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,7 +123,8 @@ export component NetplayJoin inherits Window {
|
||||
NetplayData.player_name,
|
||||
NetplayData.game_hash,
|
||||
NetplayData.password,
|
||||
NetplayData.ports[NetplayData.current_session]);
|
||||
NetplayData.room_urls[NetplayData.current_session],
|
||||
NetplayData.sessions[NetplayData.current_session][1].text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import { Page } from "page.slint";
|
||||
export global NetplayData {
|
||||
in-out property <[string]> server_names;
|
||||
in-out property <[string]> server_urls;
|
||||
in-out property <string> ping: "Ping: Unknown";
|
||||
in-out property <string> player_name;
|
||||
in-out property <string> game_name;
|
||||
in-out property <string> game_hash;
|
||||
@ -16,9 +15,10 @@ export global NetplayData {
|
||||
in-out property <string> password;
|
||||
in-out property <string> peer_addr;
|
||||
in-out property <bool> pending_session;
|
||||
in-out property <bool> pending_refresh;
|
||||
in-out property <string> session_name;
|
||||
in-out property <[[StandardListViewItem]]> sessions;
|
||||
in-out property <[int]> ports;
|
||||
in-out property <[string]> room_urls;
|
||||
in-out property <int> current_session: -1;
|
||||
in-out property <string> rom_dir;
|
||||
in-out property <string> custom_server_url;
|
||||
|
||||
@ -7,6 +7,7 @@ export global NetplayWaitData {
|
||||
in-out property <int> port;
|
||||
in-out property <bool> can_start;
|
||||
in-out property <string> chat_text;
|
||||
in-out property <string> ping;
|
||||
}
|
||||
|
||||
export component NetplayWait inherits Window {
|
||||
@ -21,6 +22,7 @@ export component NetplayWait inherits Window {
|
||||
in-out property port <=> NetplayWaitData.port;
|
||||
in-out property can_start <=> NetplayWaitData.can_start;
|
||||
in-out property chat_text <=> NetplayWaitData.chat_text;
|
||||
in-out property ping <=> NetplayWaitData.ping;
|
||||
callback send_chat_message(string);
|
||||
callback begin_game;
|
||||
|
||||
@ -37,6 +39,11 @@ export component NetplayWait inherits Window {
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: @tr("Ping: ") + NetplayWaitData.ping;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
GridBox {
|
||||
Row {
|
||||
Text {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::ui::gui::{
|
||||
AppWindow, CustomNetplayServer, ErrorDialog, GameSettings, GbPaths, NetplayCreate,
|
||||
NetplayDevice, NetplayJoin, NetplayWait, VruChannel, run_rom, save_settings,
|
||||
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, GbPaths,
|
||||
NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, VruChannel, run_rom, save_settings,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use sha2::{Digest, Sha256};
|
||||
@ -47,17 +47,14 @@ pub struct NetplayMessage {
|
||||
trait NetplayPages {
|
||||
fn set_server_names(&self, names: slint::ModelRc<slint::SharedString>);
|
||||
fn set_server_urls(&self, urls: slint::ModelRc<slint::SharedString>);
|
||||
fn set_ping(&self, ping: slint::SharedString);
|
||||
fn set_game_name(&self, game_name: slint::SharedString);
|
||||
fn set_game_hash(&self, game_hash: slint::SharedString);
|
||||
fn set_game_cheats(&self, game_cheats: slint::SharedString);
|
||||
fn set_rom_path(&self, rom_path: slint::SharedString);
|
||||
fn set_peer_addr(&self, peer_addr: slint::SharedString);
|
||||
fn refresh_sessions(&self, _server: slint::SharedString) {
|
||||
fn refresh_sessions(&self) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
fn invoke_get_ping(&self, server_url: slint::SharedString);
|
||||
fn set_custom_server_url(&self, server_url: slint::SharedString);
|
||||
}
|
||||
|
||||
impl NetplayPages for NetplayCreate {
|
||||
@ -67,9 +64,6 @@ impl NetplayPages for NetplayCreate {
|
||||
fn set_server_urls(&self, urls: slint::ModelRc<slint::SharedString>) {
|
||||
self.set_server_urls(urls);
|
||||
}
|
||||
fn set_ping(&self, ping: slint::SharedString) {
|
||||
self.set_ping(ping);
|
||||
}
|
||||
fn set_game_name(&self, game_name: slint::SharedString) {
|
||||
self.set_game_name(game_name);
|
||||
}
|
||||
@ -85,12 +79,6 @@ impl NetplayPages for NetplayCreate {
|
||||
fn set_peer_addr(&self, peer_addr: slint::SharedString) {
|
||||
self.set_peer_addr(peer_addr);
|
||||
}
|
||||
fn invoke_get_ping(&self, server_url: slint::SharedString) {
|
||||
self.invoke_get_ping(server_url);
|
||||
}
|
||||
fn set_custom_server_url(&self, server_url: slint::SharedString) {
|
||||
self.set_custom_server_url(server_url);
|
||||
}
|
||||
}
|
||||
|
||||
impl NetplayPages for NetplayJoin {
|
||||
@ -100,11 +88,8 @@ impl NetplayPages for NetplayJoin {
|
||||
fn set_server_urls(&self, urls: slint::ModelRc<slint::SharedString>) {
|
||||
self.set_server_urls(urls);
|
||||
}
|
||||
fn set_ping(&self, ping: slint::SharedString) {
|
||||
self.set_ping(ping);
|
||||
}
|
||||
fn refresh_sessions(&self, server: slint::SharedString) {
|
||||
self.invoke_refresh_session(server);
|
||||
fn refresh_sessions(&self) {
|
||||
self.invoke_refresh_session();
|
||||
}
|
||||
fn set_game_name(&self, game_name: slint::SharedString) {
|
||||
self.set_game_name(game_name);
|
||||
@ -121,16 +106,13 @@ impl NetplayPages for NetplayJoin {
|
||||
fn set_peer_addr(&self, peer_addr: slint::SharedString) {
|
||||
self.set_peer_addr(peer_addr);
|
||||
}
|
||||
fn invoke_get_ping(&self, server_url: slint::SharedString) {
|
||||
self.invoke_get_ping(server_url);
|
||||
}
|
||||
fn set_custom_server_url(&self, server_url: slint::SharedString) {
|
||||
self.set_custom_server_url(server_url);
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slint::Weak<T>) {
|
||||
let task = reqwest::get("https://cdn.gopher64.com/servers-gopher64.json");
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/getRegions")
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.send();
|
||||
tokio::spawn(async move {
|
||||
let mut local_servers: Vec<(String, String)> = vec![];
|
||||
|
||||
@ -158,10 +140,9 @@ fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slin
|
||||
}
|
||||
|
||||
let response = task.await;
|
||||
if let Ok(response) = response {
|
||||
let servers: std::collections::HashMap<String, String> = response.json().await.unwrap();
|
||||
|
||||
let weak2 = weak.clone();
|
||||
if let Ok(response) = response
|
||||
&& let Ok(servers) = response.json::<Vec<String>>().await
|
||||
{
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let server_names: slint::VecModel<slint::SharedString> = slint::VecModel::default();
|
||||
let server_urls: slint::VecModel<slint::SharedString> = slint::VecModel::default();
|
||||
@ -170,12 +151,11 @@ fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slin
|
||||
server_urls.push(local_server.1.into());
|
||||
}
|
||||
for server in servers {
|
||||
server_names.push(server.0.into());
|
||||
server_urls.push(server.1.into());
|
||||
server_names.push(server.clone().into());
|
||||
server_urls.push(format!("dispatcher:{server}").into());
|
||||
}
|
||||
server_names.push("Custom".into());
|
||||
update_ping(weak2, server_urls.row_data(0).unwrap().into());
|
||||
handle.refresh_sessions(server_urls.row_data(0).unwrap());
|
||||
handle.refresh_sessions();
|
||||
let server_names_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
|
||||
std::rc::Rc::new(server_names);
|
||||
let server_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
|
||||
@ -245,48 +225,13 @@ fn select_rom<T: ComponentHandle + NetplayPages + 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
fn update_ping<T: ComponentHandle + NetplayPages + 'static>(
|
||||
weak: slint::Weak<T>,
|
||||
server_url: String,
|
||||
) {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_ping("Ping: Unknown".into());
|
||||
})
|
||||
.unwrap();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(Ok((mut sock, _response))) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
tokio_tungstenite::connect_async(server_url),
|
||||
)
|
||||
.await
|
||||
{
|
||||
sock.send(Message::Ping(Vec::new().into())).await.unwrap();
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
if let Some(Ok(_response)) = sock.next().await {
|
||||
let elapsed = start.elapsed();
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_ping(format!("Ping: {:.0} ms", elapsed.as_millis()).into());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
sock.close(None).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_custom_url_dialog<T: ComponentHandle + NetplayPages + 'static>(
|
||||
weak: slint::Weak<T>,
|
||||
server_url: slint::SharedString,
|
||||
) {
|
||||
fn show_custom_url_dialog(weak: slint::Weak<NetplayCreate>, server_url: slint::SharedString) {
|
||||
let url_dialog = CustomNetplayServer::new().unwrap();
|
||||
url_dialog.set_custom_server_url(server_url);
|
||||
let weak_dialog = url_dialog.as_weak();
|
||||
url_dialog.on_ok_clicked(move |server_url| {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_custom_server_url(server_url.clone());
|
||||
let prefix: slint::SharedString = "ws://".into();
|
||||
handle.invoke_get_ping(prefix + &server_url);
|
||||
})
|
||||
.unwrap();
|
||||
weak_dialog.unwrap().window().hide().unwrap();
|
||||
@ -313,14 +258,9 @@ pub fn setup_create_window(
|
||||
create_window.set_rom_dir(rom_dir);
|
||||
populate_server_names(create_window.as_weak());
|
||||
let weak = create_window.as_weak();
|
||||
create_window.on_get_ping(move |server_url| {
|
||||
update_ping(weak.clone(), server_url.to_string());
|
||||
});
|
||||
let weak = create_window.as_weak();
|
||||
create_window.on_get_custom_url(move || {
|
||||
let weak2 = weak.clone();
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_ping("Ping: Unknown".into());
|
||||
show_custom_url_dialog(weak2, handle.get_custom_server_url());
|
||||
})
|
||||
.unwrap();
|
||||
@ -340,26 +280,87 @@ pub fn setup_create_window(
|
||||
game_cheats,
|
||||
password| {
|
||||
let _ = netplay_write_sender.send(None); // close current websocket if any
|
||||
manage_websocket(
|
||||
server_url.to_string(),
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
weak.clone(),
|
||||
);
|
||||
if server_url.starts_with("dispatcher:") {
|
||||
let message_dialog = DispatcherDialog::new().unwrap();
|
||||
let weak_dialog = message_dialog.as_weak();
|
||||
message_dialog.show().unwrap();
|
||||
|
||||
create_session(
|
||||
netplay_write_sender.clone(),
|
||||
netplay_read_receiver.resubscribe(),
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
game_cheats.to_string(),
|
||||
password.to_string(),
|
||||
game_settings.clone(),
|
||||
weak_app.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/createServer")
|
||||
.query(&[("region", server_url.strip_prefix("dispatcher:").unwrap())])
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.send();
|
||||
let netplay_read_sender = netplay_read_sender.clone();
|
||||
let netplay_write_receiver = netplay_write_receiver.resubscribe();
|
||||
let netplay_write_sender = netplay_write_sender.clone();
|
||||
let netplay_read_receiver = netplay_read_receiver.resubscribe();
|
||||
let game_settings = game_settings.clone();
|
||||
let weak = weak.clone();
|
||||
let weak_app = weak_app.clone();
|
||||
tokio::spawn(async move {
|
||||
let response = task.await;
|
||||
weak_dialog
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle.window().hide().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
if let Ok(response) = response
|
||||
&& let Ok(server) = response
|
||||
.json::<std::collections::HashMap<String, String>>()
|
||||
.await
|
||||
{
|
||||
let server_url = server.values().next().unwrap();
|
||||
|
||||
manage_websocket(
|
||||
server_url.to_string(),
|
||||
netplay_read_sender,
|
||||
netplay_write_receiver,
|
||||
weak.clone(),
|
||||
);
|
||||
|
||||
create_session(
|
||||
netplay_write_sender,
|
||||
netplay_read_receiver,
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
game_cheats.to_string(),
|
||||
password.to_string(),
|
||||
game_settings,
|
||||
weak_app,
|
||||
weak.clone(),
|
||||
);
|
||||
} else {
|
||||
weak.upgrade_in_event_loop(|handle| {
|
||||
handle.set_pending_session(false);
|
||||
show_netplay_error("Server could not be created".to_string());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
manage_websocket(
|
||||
server_url.to_string(),
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
weak.clone(),
|
||||
);
|
||||
|
||||
create_session(
|
||||
netplay_write_sender.clone(),
|
||||
netplay_read_receiver.resubscribe(),
|
||||
session_name.to_string(),
|
||||
player_name.to_string(),
|
||||
game_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
game_cheats.to_string(),
|
||||
password.to_string(),
|
||||
game_settings.clone(),
|
||||
weak_app.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -436,116 +437,159 @@ fn show_netplay_error(message: String) {
|
||||
message_dialog.show().unwrap();
|
||||
}
|
||||
|
||||
fn clear_sessions(handle: &NetplayJoin, message: Option<String>) {
|
||||
handle.set_sessions(slint::ModelRc::default());
|
||||
handle.set_ports(slint::ModelRc::default());
|
||||
handle.set_current_session(-1);
|
||||
if let Some(message) = message {
|
||||
show_netplay_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_sessions(
|
||||
netplay_write_sender: tokio::sync::broadcast::Sender<Option<NetplayMessage>>,
|
||||
mut netplay_read_receiver: tokio::sync::broadcast::Receiver<NetplayMessage>,
|
||||
weak: slint::Weak<NetplayJoin>,
|
||||
) {
|
||||
fn update_sessions(weak: slint::Weak<NetplayJoin>) {
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/getServers")
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.send();
|
||||
tokio::spawn(async move {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
|
||||
netplay_write_sender.send(Some(request_rooms)).unwrap();
|
||||
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
netplay_read_receiver.recv(),
|
||||
)
|
||||
.await
|
||||
let mut dispatcher_servers = std::collections::HashMap::new();
|
||||
let response = task.await;
|
||||
if let Ok(response) = response
|
||||
&& let Ok(servers) = response
|
||||
.json::<std::collections::HashMap<String, String>>()
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.accept.unwrap() == 0 {
|
||||
if let Some(rooms) = message.rooms {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let sessions_vec = slint::VecModel::default();
|
||||
let ports_vec = slint::VecModel::default();
|
||||
for room in rooms {
|
||||
let session_vec = slint::VecModel::default();
|
||||
session_vec.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.room_name.unwrap()),
|
||||
));
|
||||
session_vec.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.game_name.unwrap()),
|
||||
));
|
||||
session_vec.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(if room.protected.unwrap() {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
}),
|
||||
));
|
||||
session_vec.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(
|
||||
if room.features.unwrap_or_default().contains_key("cheats")
|
||||
{
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
),
|
||||
));
|
||||
let session_model: std::rc::Rc<
|
||||
slint::VecModel<slint::StandardListViewItem>,
|
||||
> = std::rc::Rc::new(session_vec);
|
||||
sessions_vec.push(slint::ModelRc::from(session_model));
|
||||
ports_vec.push(room.port.unwrap());
|
||||
}
|
||||
let rooms_model: std::rc::Rc<
|
||||
slint::VecModel<slint::ModelRc<slint::StandardListViewItem>>,
|
||||
> = std::rc::Rc::new(sessions_vec);
|
||||
let ports_model: std::rc::Rc<slint::VecModel<i32>> =
|
||||
std::rc::Rc::new(ports_vec);
|
||||
handle.set_sessions(slint::ModelRc::from(rooms_model));
|
||||
handle.set_ports(slint::ModelRc::from(ports_model));
|
||||
handle.set_current_session(-1);
|
||||
})
|
||||
.unwrap();
|
||||
} else {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
clear_sessions(&handle, None);
|
||||
})
|
||||
.unwrap();
|
||||
dispatcher_servers = servers;
|
||||
}
|
||||
let weak2 = weak.clone();
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let mut servers: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
let server_names = handle.get_server_names();
|
||||
let server_urls = handle.get_server_urls();
|
||||
for (i, server_name) in server_names.iter().enumerate() {
|
||||
if server_name == "Custom" {
|
||||
let custom_server_url = handle.get_custom_server_url();
|
||||
if !custom_server_url.is_empty() {
|
||||
servers.insert(
|
||||
"Custom".to_string(),
|
||||
"ws://".to_string() + &custom_server_url,
|
||||
);
|
||||
}
|
||||
} else if let Some(url) = server_urls.row_data(i)
|
||||
&& url.starts_with("dispatcher:")
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
clear_sessions(&handle, message.message);
|
||||
})
|
||||
.unwrap();
|
||||
servers.insert(
|
||||
server_name.to_string(),
|
||||
server_urls.row_data(i).unwrap().to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
panic!("netplay_read_receiver error: {err}");
|
||||
}
|
||||
Err(_) => {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
clear_sessions(&handle, Some("Server did not respond".to_string()));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
servers.extend(dispatcher_servers);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut sessions = vec![];
|
||||
let mut room_urls = vec![];
|
||||
for (server_name, server_url) in servers.iter() {
|
||||
if let Ok(Ok((socket, _response))) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
tokio_tungstenite::connect_async(server_url.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let (mut write, mut read) = socket.split();
|
||||
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
write
|
||||
.send(Message::Binary(Bytes::from(
|
||||
serde_json::to_vec(&request_rooms).unwrap(),
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Some(Ok(response)) = read.next().await {
|
||||
if let Ok(message) =
|
||||
serde_json::from_slice::<NetplayMessage>(&response.into_data())
|
||||
{
|
||||
if message.message_type == "reply_get_rooms"
|
||||
&& message.accept.unwrap() == 0
|
||||
&& let Some(rooms) = message.rooms
|
||||
{
|
||||
for room in rooms {
|
||||
let mut session = vec![];
|
||||
room_urls.push(server_url.into());
|
||||
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(server_name),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.room_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.game_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(if room.protected.unwrap() {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
}),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(
|
||||
if room
|
||||
.features
|
||||
.unwrap_or_default()
|
||||
.contains_key("cheats")
|
||||
{
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
),
|
||||
));
|
||||
sessions.push(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
weak2
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let sessions_vec = slint::VecModel::default();
|
||||
for session in sessions.iter() {
|
||||
let session_vec = slint::VecModel::from(session.to_vec());
|
||||
let session_model: std::rc::Rc<
|
||||
slint::VecModel<slint::StandardListViewItem>,
|
||||
> = std::rc::Rc::new(session_vec);
|
||||
sessions_vec.push(slint::ModelRc::from(session_model));
|
||||
}
|
||||
let rooms_model: std::rc::Rc<
|
||||
slint::VecModel<slint::ModelRc<slint::StandardListViewItem>>,
|
||||
> = std::rc::Rc::new(sessions_vec);
|
||||
handle.set_sessions(slint::ModelRc::from(rooms_model));
|
||||
|
||||
let room_urls_vec = slint::VecModel::from(room_urls.to_vec());
|
||||
let room_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
|
||||
std::rc::Rc::new(room_urls_vec);
|
||||
handle.set_room_urls(slint::ModelRc::from(room_urls_model));
|
||||
|
||||
handle.set_current_session(-1);
|
||||
handle.set_pending_refresh(false);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@ -611,7 +655,7 @@ fn create_session(
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_create_room" && message.accept.unwrap() == 0 {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let session = message.room.as_ref().unwrap();
|
||||
let features_default = "false".to_string();
|
||||
@ -686,12 +730,62 @@ fn join_session(
|
||||
player_name: String,
|
||||
game_hash: String,
|
||||
password: String,
|
||||
port: i32,
|
||||
room_name: String,
|
||||
fullscreen: bool,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
weak: slint::Weak<NetplayJoin>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
|
||||
netplay_write_sender.send(Some(request_rooms)).unwrap();
|
||||
|
||||
let mut port = 0;
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
netplay_read_receiver.recv(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.message_type == "reply_get_rooms"
|
||||
&& message.accept.unwrap() == 0
|
||||
&& let Some(rooms) = message.rooms
|
||||
{
|
||||
for room in rooms {
|
||||
if room.room_name.unwrap() == room_name {
|
||||
port = room.port.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
panic!("netplay_read_receiver error: {err}");
|
||||
}
|
||||
Err(_) => {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_pending_session(false);
|
||||
show_netplay_error("Server did not respond".to_string());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let join_room = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
player_name: Some(player_name),
|
||||
@ -725,7 +819,7 @@ fn join_session(
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_join_room" && message.accept.unwrap() == 0 {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let session = message.room.as_ref().unwrap();
|
||||
let features_default = "false".to_string();
|
||||
@ -817,6 +911,7 @@ fn setup_wait_window(
|
||||
wait.set_rom_path(rom_path);
|
||||
wait.set_port(port);
|
||||
wait.set_can_start(can_start);
|
||||
wait.set_ping("Unknown".into());
|
||||
|
||||
let sender = netplay_write_sender.clone();
|
||||
wait.on_send_chat_message(move |message| {
|
||||
@ -897,11 +992,44 @@ fn setup_wait_window(
|
||||
|
||||
netplay_write_sender.send(Some(motd_message)).unwrap();
|
||||
|
||||
let ping_message = NetplayMessage {
|
||||
message_type: "request_ping".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
rooms: None,
|
||||
player_names: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: None,
|
||||
};
|
||||
|
||||
netplay_write_sender
|
||||
.send(Some(ping_message.clone()))
|
||||
.unwrap();
|
||||
|
||||
let weak = wait.as_weak();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match netplay_read_receiver.recv().await {
|
||||
Ok(response) => match response.message_type.as_str() {
|
||||
"reply_ping" => {
|
||||
if let Some(message) = response.message {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_ping((message + " ms").into());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let ping_message = ping_message.clone();
|
||||
let ping_writer = netplay_write_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
let _ = ping_writer.send(Some(ping_message));
|
||||
});
|
||||
}
|
||||
"reply_motd" => {
|
||||
let request_players = NetplayMessage {
|
||||
message_type: "request_players".to_string(),
|
||||
@ -1047,26 +1175,10 @@ pub fn setup_join_window(
|
||||
tokio::sync::broadcast::Receiver<Option<NetplayMessage>>,
|
||||
) = tokio::sync::broadcast::channel(5);
|
||||
|
||||
join_window.set_pending_refresh(true);
|
||||
join_window.set_rom_dir(rom_dir);
|
||||
populate_server_names(join_window.as_weak());
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_get_ping(move |server_url| {
|
||||
update_ping(weak.clone(), server_url.to_string());
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.invoke_refresh_session(server_url);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_get_custom_url(move || {
|
||||
let weak2 = weak.clone();
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_ping("Ping: Unknown".into());
|
||||
show_custom_url_dialog(weak2, handle.get_custom_server_url());
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_select_rom(move |rom_dir| {
|
||||
select_rom(weak.clone(), rom_dir);
|
||||
});
|
||||
@ -1076,34 +1188,34 @@ pub fn setup_join_window(
|
||||
let _ = sender.send(None); // close current websocket if any
|
||||
slint::CloseRequestResponse::HideWindow
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_refresh_session(move || {
|
||||
update_sessions(weak.clone());
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_join_session(
|
||||
move |player_name, game_hash, password, room_url, room_name| {
|
||||
let _ = netplay_write_sender.send(None); // close current websocket if any
|
||||
manage_websocket(
|
||||
room_url.to_string(),
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
weak.clone(),
|
||||
);
|
||||
|
||||
let weak = join_window.as_weak();
|
||||
let sender = netplay_write_sender.clone();
|
||||
let receiver = netplay_read_receiver.resubscribe();
|
||||
join_window.on_refresh_session(move |server_url| {
|
||||
let _ = sender.send(None); // close current websocket if any
|
||||
manage_websocket(
|
||||
server_url.to_string(),
|
||||
netplay_read_sender.clone(),
|
||||
netplay_write_receiver.resubscribe(),
|
||||
weak.clone(),
|
||||
);
|
||||
update_sessions(sender.clone(), receiver.resubscribe(), weak.clone());
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_join_session(move |player_name, game_hash, password, port| {
|
||||
join_session(
|
||||
netplay_write_sender.clone(),
|
||||
netplay_read_receiver.resubscribe(),
|
||||
player_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
password.to_string(),
|
||||
port,
|
||||
fullscreen,
|
||||
weak_app.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
});
|
||||
join_session(
|
||||
netplay_write_sender.clone(),
|
||||
netplay_read_receiver.resubscribe(),
|
||||
player_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
password.to_string(),
|
||||
room_name.to_string(),
|
||||
fullscreen,
|
||||
weak_app.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
join_window.show().unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user