Skip to content

Instantly share code, notes, and snippets.

@CryZe
Created September 20, 2024 19:23
Show Gist options
  • Save CryZe/c962f0dd6c10466a5dd4fb5759d8013d to your computer and use it in GitHub Desktop.
Save CryZe/c962f0dd6c10466a5dd4fb5759d8013d to your computer and use it in GitHub Desktop.
Rough websocket server that hosts the auto splitting runtime.
[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"
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