Created
September 20, 2024 19:23
-
-
Save CryZe/c962f0dd6c10466a5dd4fb5759d8013d to your computer and use it in GitHub Desktop.
Rough websocket server that hosts the auto splitting runtime.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "liveplit-one-auto-splitter-server" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
livesplit-auto-splitting = { path = "P:\\livesplit-core\\crates\\livesplit-auto-splitting" } | |
tokio-tungstenite = "0.21.0" | |
tokio = { version = "1.37.0", features = ["macros", "rt", "sync"] } | |
futures-util = "0.3.30" | |
serde_json = "1.0.117" | |
serde = "1.0.203" | |
serde_derive = "1.0.203" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{fmt, net::Ipv4Addr, sync::Arc, thread}; | |
use futures_util::{SinkExt, StreamExt}; | |
use livesplit_auto_splitting::{settings, time, Runtime, Timer, TimerState}; | |
use tokio::{ | |
net::TcpListener, | |
sync::mpsc::{self, UnboundedSender}, | |
}; | |
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; | |
#[tokio::main(flavor = "current_thread")] | |
async fn main() { | |
let runtime = Runtime::new(Default::default()).unwrap(); | |
let auto_splitter = Arc::new( | |
runtime | |
// .compile(include_bytes!("../lunistice_auto_splitter.wasm")) | |
.compile(include_bytes!("../furious_fish_auto_splitter.wasm")) | |
.unwrap(), | |
); | |
let listener = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 8081)) | |
.await | |
.unwrap(); | |
loop { | |
let (connection, _) = listener.accept().await.unwrap(); | |
println!("Connection established"); | |
let (sender, mut receiver) = mpsc::unbounded_channel(); | |
let sender2 = sender.clone(); | |
let sender3 = sender.clone(); | |
let auto_splitter = auto_splitter.clone(); | |
tokio::task::spawn(async move { | |
let websocket = tokio_tungstenite::accept_async(connection).await.unwrap(); | |
let (mut writer, mut reader) = websocket.split(); | |
let _ = writer | |
.send(tokio_tungstenite::tungstenite::Message::Text( | |
r#"{"command":"getCurrentState"}"#.into(), | |
)) | |
.await; | |
let timer_state = loop { | |
let Some(Ok(message)) = reader.next().await else { | |
println!("auto splitter shutdown"); | |
return; | |
}; | |
if let tokio_tungstenite::tungstenite::Message::Close(_) = message { | |
println!("auto splitter shutdown"); | |
return; | |
} | |
if let tokio_tungstenite::tungstenite::Message::Text(message) = message { | |
if message.starts_with(r#"{"success":{"state":"NotRunning""#) { | |
break TimerState::NotRunning; | |
} | |
if message.starts_with(r#"{"success":{"state":"Running""#) { | |
break TimerState::Running; | |
} | |
if message.starts_with(r#"{"success":{"state":"Paused""#) { | |
break TimerState::Paused; | |
} | |
if message.starts_with(r#"{"success":{"state":"Ended""#) { | |
break TimerState::Ended; | |
} | |
} | |
}; | |
let timer_state = Arc::new(std::sync::Mutex::new(dbg!(timer_state))); | |
let mut settings_map = settings::Map::new(); | |
settings_map.insert("when".into(), settings::Value::String("Meters25".into())); | |
let auto_splitter = auto_splitter | |
.instantiate( | |
WsTimer(sender, timer_state.clone()), | |
Some(settings_map), | |
None, | |
) | |
.unwrap(); | |
tokio::task::spawn(async move { | |
while let Some(Ok(message)) = reader.next().await { | |
if let tokio_tungstenite::tungstenite::Message::Close(_) = message { | |
break; | |
} | |
if let tokio_tungstenite::tungstenite::Message::Text(message) = message { | |
if let Ok(event) = serde_json::from_str::<IsEvent>(&message) { | |
let new_timer_state = match event.event { | |
Event::Started => TimerState::Running, | |
Event::SplitUndone => TimerState::Running, | |
Event::Resumed => TimerState::Running, | |
Event::Paused => TimerState::Paused, | |
Event::Finished => TimerState::Ended, | |
Event::Reset => TimerState::NotRunning, | |
Event::PausesUndoneAndResumed => TimerState::Running, | |
_ => continue, | |
}; | |
*timer_state.lock().unwrap() = dbg!(new_timer_state); | |
} | |
} | |
} | |
let _ = sender2.send(CommandOrClose::Close); | |
}); | |
thread::spawn(move || { | |
while !sender3.is_closed() { | |
auto_splitter.lock().update().unwrap(); | |
thread::sleep(auto_splitter.tick_rate()); | |
} | |
println!("auto splitter shutdown"); | |
}); | |
while let Some(event) = receiver.recv().await { | |
let command = match event { | |
CommandOrClose::Close => break, | |
CommandOrClose::Command(command) => command, | |
}; | |
// println!("{message}"); | |
let _ = writer | |
.send(tokio_tungstenite::tungstenite::Message::Text( | |
serde_json::to_string(&command).unwrap(), | |
)) | |
.await; | |
} | |
let _ = writer | |
.send(tokio_tungstenite::tungstenite::Message::Close(Some( | |
tokio_tungstenite::tungstenite::protocol::CloseFrame { | |
code: CloseCode::Away, | |
reason: "Lol okay :)".into(), | |
}, | |
))) | |
.await; | |
}); | |
} | |
} | |
fn fmt_time(time: time::Duration) -> String { | |
let (secs, nanos) = (time.whole_seconds(), time.subsec_nanoseconds()); | |
format!( | |
"{}{}.{:09}", | |
if nanos as i64 | secs < 0 { "-" } else { "" }, | |
secs.unsigned_abs(), | |
nanos.abs() | |
) | |
} | |
enum CommandOrClose { | |
Command(Command), | |
Close, | |
} | |
impl From<Command> for CommandOrClose { | |
fn from(command: Command) -> Self { | |
Self::Command(command) | |
} | |
} | |
#[derive(serde_derive::Serialize)] | |
#[serde(tag = "command", rename_all = "camelCase")] | |
#[allow(unused)] | |
enum Command { | |
SplitOrStart, | |
Split, | |
UndoSplit, | |
SkipSplit, | |
Pause, | |
Resume, | |
TogglePauseOrStart, | |
Reset, | |
Start, | |
InitializeGameTime, | |
SetGameTime { | |
time: String, | |
}, | |
SetLoadingTimes { | |
time: String, | |
}, | |
PauseGameTime, | |
ResumeGameTime, | |
SetCustomVariable { | |
key: String, | |
value: String, | |
}, | |
SetCurrentComparison { | |
comparison: String, | |
}, | |
// #[serde(rename_all = "camelCase")] | |
// SetCurrentTimingMethod { | |
// timing_method: TimingMethod, | |
// }, | |
#[serde(rename_all = "camelCase")] | |
GetCurrentTime { | |
// timing_method: Option<TimingMethod>, | |
}, | |
GetSegmentName { | |
index: Option<isize>, | |
#[serde(default)] | |
relative: bool, | |
}, | |
#[serde(rename_all = "camelCase")] | |
GetComparisonTime { | |
index: Option<isize>, | |
#[serde(default)] | |
relative: bool, | |
comparison: Option<String>, | |
// timing_method: Option<TimingMethod>, | |
}, | |
#[serde(rename_all = "camelCase")] | |
GetCurrentRunSplitTime { | |
index: Option<isize>, | |
#[serde(default)] | |
relative: bool, | |
// timing_method: Option<TimingMethod>, | |
}, | |
GetCurrentState, | |
Ping, | |
} | |
#[derive( | |
Copy, Clone, Debug, PartialEq, Eq, Hash, serde_derive::Serialize, serde_derive::Deserialize, | |
)] | |
#[non_exhaustive] | |
pub enum Event { | |
/// The timer has been started. | |
Started = 0, | |
/// A split happened. Note that the final split is signaled by | |
/// [`Finished`]. | |
Splitted = 1, | |
/// The final split happened, the run is now finished, but has not been | |
/// reset yet. | |
Finished = 2, | |
/// The timer has been reset. | |
Reset = 3, | |
/// The previous split has been undone. | |
SplitUndone = 4, | |
/// The current split has been skipped. | |
SplitSkipped = 5, | |
/// The timer has been paused. | |
Paused = 6, | |
/// The timer has been resumed. | |
Resumed = 7, | |
/// All the pauses have been undone. | |
PausesUndone = 8, | |
/// All the pauses have been undone and the timer has been resumed. | |
PausesUndoneAndResumed = 9, | |
/// The comparison has been changed. | |
ComparisonChanged = 10, | |
/// The timing method has been changed. | |
TimingMethodChanged = 11, | |
/// The game time has been initialized. | |
GameTimeInitialized = 12, | |
/// The game time has been set. | |
GameTimeSet = 13, | |
/// The game time has been paused. | |
GameTimePaused = 14, | |
/// The game time has been resumed. | |
GameTimeResumed = 15, | |
/// The loading times have been set. | |
LoadingTimesSet = 16, | |
/// A custom variable has been set. | |
CustomVariableSet = 17, | |
} | |
#[derive(serde_derive::Deserialize)] | |
struct IsEvent { | |
event: Event, | |
} | |
struct WsTimer( | |
UnboundedSender<CommandOrClose>, | |
Arc<std::sync::Mutex<TimerState>>, | |
); | |
impl Timer for WsTimer { | |
fn state(&self) -> TimerState { | |
*self.1.lock().unwrap() | |
} | |
fn start(&mut self) { | |
let _ = self.0.send(Command::Start.into()); | |
} | |
fn split(&mut self) { | |
let _ = self.0.send(Command::Split.into()); | |
} | |
fn skip_split(&mut self) { | |
let _ = self.0.send(Command::SkipSplit.into()); | |
} | |
fn undo_split(&mut self) { | |
let _ = self.0.send(Command::UndoSplit.into()); | |
} | |
fn reset(&mut self) { | |
let _ = self.0.send(Command::Reset.into()); | |
} | |
fn set_game_time(&mut self, time: time::Duration) { | |
let _ = self.0.send( | |
Command::SetGameTime { | |
time: fmt_time(time), | |
} | |
.into(), | |
); | |
} | |
fn pause_game_time(&mut self) { | |
let _ = self.0.send(Command::PauseGameTime.into()); | |
} | |
fn resume_game_time(&mut self) { | |
let _ = self.0.send(Command::ResumeGameTime.into()); | |
} | |
fn set_variable(&mut self, key: &str, value: &str) { | |
let _ = self.0.send( | |
Command::SetCustomVariable { | |
key: key.into(), | |
value: value.into(), | |
} | |
.into(), | |
); | |
} | |
fn log_auto_splitter(&mut self, message: fmt::Arguments<'_>) { | |
println!("{message}"); | |
} | |
fn log_runtime(&mut self, message: fmt::Arguments<'_>) { | |
println!("{message}"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment