From 2ae7a81f2267ac685616747acd9141550932fe3c Mon Sep 17 00:00:00 2001 From: 4yn Date: Wed, 9 Feb 2022 14:00:36 +0800 Subject: [PATCH] async output and led --- src-tauri/Cargo.lock | 80 ++++++++++++++-- src-tauri/Cargo.toml | 33 ++++--- src-tauri/src/main.rs | 15 +-- src-tauri/src/slider_io/brokenithm.rs | 8 +- src-tauri/src/slider_io/config.rs | 100 ++++++++++++-------- src-tauri/src/slider_io/context.rs | 18 ++-- src-tauri/src/slider_io/controller_state.rs | 10 +- src-tauri/src/slider_io/device.rs | 33 ++++--- src-tauri/src/slider_io/gamepad.rs | 64 ++++++++++--- src-tauri/src/slider_io/keyboard.rs | 19 ++++ src-tauri/src/slider_io/led.rs | 53 ++++++----- src-tauri/src/slider_io/manager.rs | 15 +-- src-tauri/src/slider_io/output.rs | 28 +++--- src-tauri/src/slider_io/worker.rs | 78 ++++++++++++--- src-tauri/tauri.conf.json | 2 +- src/App.svelte | 5 +- 16 files changed, 393 insertions(+), 168 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 26e170f..79844e3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1666,9 +1666,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -2132,7 +2132,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", ] [[package]] @@ -2150,10 +2160,17 @@ dependencies = [ ] [[package]] -name = "path-clean" -version = "0.1.0" +name = "parking_lot_core" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.10", + "smallvec", + "windows-sys", +] [[package]] name = "pathdiff" @@ -2939,7 +2956,7 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slidershim" -version = "0.1.3" +version = "0.1.4" dependencies = [ "async-trait", "atomic_float", @@ -2954,7 +2971,7 @@ dependencies = [ "log", "open", "palette", - "path-clean", + "parking_lot 0.12.0", "phf 0.10.1", "qrcode", "rusb", @@ -3037,7 +3054,7 @@ checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" dependencies = [ "lazy_static", "new_debug_unreachable", - "parking_lot", + "parking_lot 0.11.2", "phf_shared 0.8.0", "precomputed-hash", "serde", @@ -3177,7 +3194,7 @@ dependencies = [ "ndk-glue", "ndk-sys", "objc", - "parking_lot", + "parking_lot 0.11.2", "raw-window-handle 0.3.4", "scopeguard", "serde", @@ -3971,6 +3988,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "winreg" version = "0.7.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5fe8dc0..3421817 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "slidershim" -version = "0.1.3" +version = "0.1.4" description = "slidershim" authors = ["4yn"] license = "" @@ -9,45 +9,54 @@ default-run = "slidershim" edition = "2018" build = "src/build.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [build-dependencies] tauri-build = { version = "1.0.0-beta.4" } [dependencies] -serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } + +# logging log = "0.4.14" env_logger = "0.9.0" simple-logging = "2.0.2" -open = "2.0.2" + +# threads +parking_lot = "0.12.0" atomic_float = "0.1.0" spin_sleep = "1.0.0" -tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] } +# async futures = "0.3.19" futures-util = "0.3.19" async-trait = "0.1.52" tokio = { version="1.16.1", features= ["rt-multi-thread","macros"] } tokio-util = "0.6.9" +# UI +tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] } +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +open = "2.0.2" directories = "4.0.1" +image = "0.23.14" + +# device and system rusb = "0.9.0" serialport = "4.0.1" vigem-client = "0.1.1" -palette = "0.6.0" winapi = "0.3.9" ipconfig = "0.3.0" +# webserver hyper = { version="0.14.16", features= ["server", "http1", "http2", "tcp", "stream", "runtime"] } phf = { version = "0.10.1", features = ["macros"] } -base64 = "0.13.0" -image = "0.23.14" -qrcode = { version="0.12.0", features= ["image"] } -path-clean = "0.1.0" tungstenite = { version="0.16.0", default-features=false } tokio-tungstenite = "0.16.1" +# webserver utils +base64 = "0.13.0" +palette = "0.6.0" +qrcode = { version="0.12.0", features= ["image"] } + [features] default = [ "custom-protocol" ] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 249f022..a6707dd 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,8 @@ mod slider_io; -use std::sync::{Arc, Mutex}; +use parking_lot::Mutex; +use std::sync::Arc; use log::info; @@ -46,10 +47,10 @@ fn main() { let config = Arc::new(Mutex::new(Some(slider_io::Config::load()))); let manager = Arc::new(Mutex::new(slider_io::Manager::new())); { - let config_handle = config.lock().unwrap(); + let config_handle = config.lock(); let config_handle_ref = config_handle.as_ref().unwrap(); config_handle_ref.save(); - let manager_handle = manager.lock().unwrap(); + let manager_handle = manager.lock(); manager_handle.update_config(config_handle_ref.clone()); } @@ -119,7 +120,7 @@ fn main() { let app_handle = app.handle(); let config_clone = Arc::clone(&config); app.listen_global("ready", move |_| { - let config_handle = config_clone.lock().unwrap(); + let config_handle = config_clone.lock(); info!("Start signal received"); app_handle .emit_all( @@ -140,7 +141,7 @@ fn main() { app.listen_global("queryState", move |_| { // app_handle.emit_all("showState", "@@@"); let (snapshot, timer) = { - let manager_handle = manager_clone.lock().unwrap(); + let manager_handle = manager_clone.lock(); ( manager_handle.try_get_state().map(|x| x.snapshot()), manager_handle.get_timer_state(), @@ -163,12 +164,12 @@ fn main() { let payload = event.payload().unwrap(); info!("Config applied {}", payload); if let Some(new_config) = slider_io::Config::from_str(payload) { - let mut config_handle = config_clone.lock().unwrap(); + let mut config_handle = config_clone.lock(); config_handle.take(); config_handle.replace(new_config); let config_handle_ref = config_handle.as_ref().unwrap(); config_handle_ref.save(); - let manager_handle = manager_clone.lock().unwrap(); + let manager_handle = manager_clone.lock(); manager_handle.update_config(config_handle_ref.clone()); } }); diff --git a/src-tauri/src/slider_io/brokenithm.rs b/src-tauri/src/slider_io/brokenithm.rs index 7d70ae4..8b7b089 100644 --- a/src-tauri/src/slider_io/brokenithm.rs +++ b/src-tauri/src/slider_io/brokenithm.rs @@ -18,7 +18,7 @@ use tokio::{ use tokio_tungstenite::WebSocketStream; use tungstenite::{handshake, Message}; -use crate::slider_io::{controller_state::FullState, worker::AsyncJob}; +use crate::slider_io::{controller_state::FullState, worker::AsyncHaltableJob}; // https://levelup.gitconnected.com/handling-websocket-and-http-on-the-same-port-with-rust-f65b770722c9 @@ -114,7 +114,7 @@ async fn handle_brokenithm( } 39 => { if chars[0] == 'b' { - let mut controller_state_handle = state_handle.controller_state.lock().unwrap(); + let mut controller_state_handle = state_handle.controller_state.lock(); for (idx, c) in chars[0..32].iter().enumerate() { controller_state_handle.ground_state[idx] = match *c == '1' { false => 0, @@ -167,7 +167,7 @@ async fn handle_brokenithm( loop { let mut led_data = vec![0; 93]; { - let led_state_handle = state_handle.led_state.lock().unwrap(); + let led_state_handle = state_handle.led_state.lock(); (&mut led_data).copy_from_slice(&led_state_handle.led_state); } msg_write_handle.send(Message::Binary(led_data)).ok(); @@ -273,7 +273,7 @@ impl BrokenithmJob { } #[async_trait] -impl AsyncJob for BrokenithmJob { +impl AsyncHaltableJob for BrokenithmJob { async fn run + Send>(self, stop_signal: F) { let state = self.state.clone(); let ground_only = self.ground_only; diff --git a/src-tauri/src/slider_io/config.rs b/src-tauri/src/slider_io/config.rs index e29996f..0e398bf 100644 --- a/src-tauri/src/slider_io/config.rs +++ b/src-tauri/src/slider_io/config.rs @@ -8,11 +8,18 @@ use std::{convert::TryFrom, fs, path::PathBuf}; use crate::slider_io::utils::list_ips; #[derive(Debug, Clone)] -pub enum DeviceMode { - None, +pub enum HardwareSpec { TasollerOne, TasollerTwo, Yuancon, +} + +#[derive(Debug, Clone)] +pub enum DeviceMode { + None, + Hardware { + spec: HardwareSpec, + }, Brokenithm { ground_only: bool, led_enabled: bool, @@ -20,7 +27,7 @@ pub enum DeviceMode { } #[derive(Debug, Clone, Copy)] -pub enum OutputPolling { +pub enum PollingRate { Sixty, Hundred, TwoHundredFifty, @@ -28,35 +35,13 @@ pub enum OutputPolling { Thousand, } -impl OutputPolling { - pub fn from_str(s: &str) -> Option { - match s { - "60" => Some(OutputPolling::Sixty), - "100" => Some(OutputPolling::Hundred), - "250" => Some(OutputPolling::TwoHundredFifty), - "500" => Some(OutputPolling::FiveHundred), - "1000" => Some(OutputPolling::Thousand), - _ => None, - } - } - - pub fn to_t_u64(&self) -> u64 { - match self { - OutputPolling::Sixty => 16666, - OutputPolling::Hundred => 10000, - OutputPolling::TwoHundredFifty => 4000, - OutputPolling::FiveHundred => 2000, - OutputPolling::Thousand => 1000, - } - } -} - #[derive(Debug, Clone, Copy)] pub enum KeyboardLayout { Tasoller, Yuancon, Deemo, Voltex, + Neardayo, } #[derive(Debug, Clone, Copy)] @@ -65,22 +50,45 @@ pub enum GamepadLayout { Neardayo, } +impl PollingRate { + pub fn from_str(s: &str) -> Option { + match s { + "60" => Some(PollingRate::Sixty), + "100" => Some(PollingRate::Hundred), + "250" => Some(PollingRate::TwoHundredFifty), + "500" => Some(PollingRate::FiveHundred), + "1000" => Some(PollingRate::Thousand), + _ => None, + } + } + + pub fn to_t_u64(&self) -> u64 { + match self { + PollingRate::Sixty => 16666, + PollingRate::Hundred => 10000, + PollingRate::TwoHundredFifty => 4000, + PollingRate::FiveHundred => 2000, + PollingRate::Thousand => 1000, + } + } +} + #[derive(Debug, Clone)] pub enum OutputMode { None, Keyboard { layout: KeyboardLayout, - polling: OutputPolling, + polling: PollingRate, sensitivity: u8, }, Gamepad { layout: GamepadLayout, - polling: OutputPolling, + polling: PollingRate, sensitivity: u8, }, Websocket { url: String, - polling: OutputPolling, + polling: PollingRate, }, } @@ -123,9 +131,15 @@ impl Config { raw: s.to_string(), device_mode: match v["deviceMode"].as_str()? { "none" => DeviceMode::None, - "tasoller-one" => DeviceMode::TasollerOne, - "tasoller-two" => DeviceMode::TasollerTwo, - "yuancon" => DeviceMode::Yuancon, + "tasoller-one" => DeviceMode::Hardware { + spec: HardwareSpec::TasollerOne, + }, + "tasoller-two" => DeviceMode::Hardware { + spec: HardwareSpec::TasollerTwo, + }, + "yuancon" => DeviceMode::Hardware { + spec: HardwareSpec::Yuancon, + }, "brokenithm" => DeviceMode::Brokenithm { ground_only: false, led_enabled: false, @@ -148,37 +162,42 @@ impl Config { "none" => OutputMode::None, "kb-32-tasoller" => OutputMode::Keyboard { layout: KeyboardLayout::Tasoller, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "kb-32-yuancon" => OutputMode::Keyboard { layout: KeyboardLayout::Yuancon, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "kb-8-deemo" => OutputMode::Keyboard { layout: KeyboardLayout::Deemo, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "kb-voltex" => OutputMode::Keyboard { layout: KeyboardLayout::Voltex, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, + sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, + }, + "kb-neardayo" => OutputMode::Keyboard { + layout: KeyboardLayout::Neardayo, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "gamepad-voltex" => OutputMode::Gamepad { layout: GamepadLayout::Voltex, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "gamepad-neardayo" => OutputMode::Gamepad { layout: GamepadLayout::Neardayo, - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, }, "websocket" => OutputMode::Websocket { url: v["outputWebsocketUrl"].as_str()?.to_string(), - polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?, + polling: PollingRate::from_str(v["outputPolling"].as_str()?)?, }, _ => panic!("Invalid output mode"), }, @@ -259,11 +278,12 @@ impl Config { Self::from_str( r#"{ "deviceMode": "none", + "devicePolling": "100", "outputMode": "none", "ledMode": "none", "keyboardSensitivity": 20, "outputWebsocketUrl": "localhost:3000", - "outputPolling": "60", + "outputPolling": "100", "ledSensitivity": 20, "ledWebsocketUrl": "localhost:3001", "ledSerialPort": "COM5" diff --git a/src-tauri/src/slider_io/context.rs b/src-tauri/src/slider_io/context.rs index 1731b2c..6cf7f73 100644 --- a/src-tauri/src/slider_io/context.rs +++ b/src-tauri/src/slider_io/context.rs @@ -10,7 +10,7 @@ use crate::slider_io::{ led::LedJob, output::OutputJob, utils::LoopTimer, - worker::{AsyncWorker, ThreadWorker}, + worker::{AsyncHaltableWorker, AsyncWorker, ThreadWorker}, }; #[allow(dead_code)] @@ -18,9 +18,9 @@ pub struct Context { state: FullState, config: Config, device_worker: Option, - brokenithm_worker: Option, - output_worker: Option, - led_worker: Option, + brokenithm_worker: Option, + output_worker: Option, + led_worker: Option, timers: Vec<(&'static str, Arc)>, } @@ -41,18 +41,18 @@ impl Context { led_enabled, } => ( None, - Some(AsyncWorker::new( + Some(AsyncHaltableWorker::new( "brokenithm", BrokenithmJob::new(&state, ground_only, led_enabled), )), ), - _ => ( + DeviceMode::Hardware { spec } => ( { let timer = LoopTimer::new(); timers.push(("d", timer.fork())); Some(ThreadWorker::new( "device", - HidDeviceJob::from_config(&state, &config.device_mode), + HidDeviceJob::from_config(&state, spec), timer, )) }, @@ -64,7 +64,7 @@ impl Context { _ => { let timer = LoopTimer::new(); timers.push(("o", timer.fork())); - Some(ThreadWorker::new( + Some(AsyncWorker::new( "output", OutputJob::new(&state, &config.output_mode), timer, @@ -76,7 +76,7 @@ impl Context { _ => { let timer = LoopTimer::new(); timers.push(("l", timer.fork())); - Some(ThreadWorker::new( + Some(AsyncWorker::new( "led", LedJob::new(&state, &config.led_mode), timer, diff --git a/src-tauri/src/slider_io/controller_state.rs b/src-tauri/src/slider_io/controller_state.rs index 60eb4b7..cc30383 100644 --- a/src-tauri/src/slider_io/controller_state.rs +++ b/src-tauri/src/slider_io/controller_state.rs @@ -1,7 +1,5 @@ -use std::{ - sync::{Arc, Mutex}, - time::Instant, -}; +use parking_lot::Mutex; +use std::{sync::Arc, time::Instant}; pub struct ControllerState { pub ground_state: [u8; 32], @@ -84,13 +82,13 @@ impl FullState { pub fn snapshot(&self) -> Vec { let mut buf: Vec = vec![]; { - let controller_state_handle = self.controller_state.lock().unwrap(); + let controller_state_handle = self.controller_state.lock(); buf.extend(controller_state_handle.ground_state); buf.extend(controller_state_handle.air_state); buf.extend(controller_state_handle.extra_state); }; { - let led_state_handle = self.led_state.lock().unwrap(); + let led_state_handle = self.led_state.lock(); buf.extend(led_state_handle.led_state); }; diff --git a/src-tauri/src/slider_io/device.rs b/src-tauri/src/slider_io/device.rs index 8890009..c5c27a2 100644 --- a/src-tauri/src/slider_io/device.rs +++ b/src-tauri/src/slider_io/device.rs @@ -2,12 +2,13 @@ use log::{error, info}; use rusb::{self, DeviceHandle, GlobalContext}; use std::{ error::Error, + mem::swap, ops::{Deref, DerefMut}, time::Duration, }; use crate::slider_io::{ - config::DeviceMode, + config::HardwareSpec, controller_state::{ControllerState, FullState, LedState}, utils::{Buffer, ShimError}, worker::ThreadJob, @@ -23,6 +24,7 @@ enum WriteType { pub struct HidDeviceJob { state: FullState, + vid: u16, pid: u16, read_endpoint: u8, @@ -30,6 +32,7 @@ pub struct HidDeviceJob { read_callback: HidReadCallback, read_buf: Buffer, + last_read_buf: Buffer, led_write_type: WriteType, led_callback: HidLedCallback, @@ -57,6 +60,7 @@ impl HidDeviceJob { led_endpoint, read_callback, read_buf: Buffer::new(), + last_read_buf: Buffer::new(), led_write_type: led_type, led_callback, led_buf: Buffer::new(), @@ -64,9 +68,9 @@ impl HidDeviceJob { } } - pub fn from_config(state: &FullState, mode: &DeviceMode) -> Self { - match mode { - DeviceMode::TasollerOne => Self::new( + pub fn from_config(state: &FullState, spec: &HardwareSpec) -> Self { + match spec { + HardwareSpec::TasollerOne => Self::new( state.clone(), 0x1ccf, 0x2333, @@ -108,7 +112,7 @@ impl HidDeviceJob { buf.data[96..240].fill(0); }, ), - DeviceMode::TasollerTwo => Self::new( + HardwareSpec::TasollerTwo => Self::new( state.clone(), 0x1ccf, 0x2333, @@ -146,7 +150,7 @@ impl HidDeviceJob { buf.data[96..240].fill(0); }, ), - DeviceMode::Yuancon => Self::new( + HardwareSpec::Yuancon => Self::new( state.clone(), 0x1973, 0x2001, @@ -164,7 +168,7 @@ impl HidDeviceJob { controller_state.air_state[i ^ 1] = (buf.data[0] >> i) & 1; } for i in 0..3 { - controller_state.extra_state[i] = (buf.data[1] >> i) & 1; + controller_state.extra_state[2 - i] = (buf.data[1] >> i) & 1; } }, WriteType::Interrupt, @@ -181,7 +185,6 @@ impl HidDeviceJob { } }, ), - _ => panic!("Not implemented"), } } @@ -239,17 +242,19 @@ impl ThreadJob for HidDeviceJob { .unwrap_or(0); self.read_buf.len = res; // debug!("{:?}", self.read_buf.slice()); - if self.read_buf.len != 0 { + // if self.read_buf.len != 0 { + if (self.read_buf.len != 0) && (self.read_buf.slice() != self.last_read_buf.slice()) { work = true; - let mut controller_state_handle = self.state.controller_state.lock().unwrap(); + let mut controller_state_handle = self.state.controller_state.lock(); (self.read_callback)(&self.read_buf, controller_state_handle.deref_mut()); + swap(&mut self.read_buf, &mut self.last_read_buf); } } // Led loop { { - let mut led_state_handle = self.state.led_state.lock().unwrap(); + let mut led_state_handle = self.state.led_state.lock(); if led_state_handle.dirty { (self.led_callback)(&mut self.led_buf, led_state_handle.deref()); led_state_handle.dirty = false; @@ -269,7 +274,7 @@ impl ThreadJob for HidDeviceJob { }) .unwrap_or(0); if res == self.led_buf.len + 1 { - work = true; + // work = true; self.led_buf.len = 0; } } @@ -277,8 +282,10 @@ impl ThreadJob for HidDeviceJob { work } +} - fn teardown(&mut self) { +impl Drop for HidDeviceJob { + fn drop(&mut self) { if let Some(handle) = self.handle.as_mut() { handle.release_interface(0).ok(); } diff --git a/src-tauri/src/slider_io/gamepad.rs b/src-tauri/src/slider_io/gamepad.rs index 0bd9c34..1e74e6d 100644 --- a/src-tauri/src/slider_io/gamepad.rs +++ b/src-tauri/src/slider_io/gamepad.rs @@ -4,10 +4,48 @@ use vigem_client::{Client, TargetId, XButtons, XGamepad, Xbox360Wired}; use crate::slider_io::{config::GamepadLayout, output::OutputHandler, voltex::VoltexState}; +struct LastWind { + left: bool, + right: bool, + out: i16, +} + +impl LastWind { + fn new() -> Self { + LastWind { + left: false, + right: false, + out: 0, + } + } + + fn update(&mut self, left: bool, right: bool) -> i16 { + let out = match (left, right) { + (false, false) => 0, + (true, false) => -1, + (false, true) => 1, + (true, true) => match (self.left, self.right) { + (false, false) => 0, + (true, false) => 1, + (false, true) => -1, + (true, true) => self.out, + }, + }; + + self.left = left; + self.right = right; + self.out = out; + + out + } +} + pub struct GamepadOutput { target: Xbox360Wired, use_air: bool, gamepad: XGamepad, + left_wind: LastWind, + right_wind: LastWind, } impl GamepadOutput { @@ -23,6 +61,8 @@ impl GamepadOutput { target, use_air, gamepad: XGamepad::default(), + left_wind: LastWind::new(), + right_wind: LastWind::new(), }), Err(e) => { error!("Gamepad connection error: {}", e); @@ -80,21 +120,17 @@ impl OutputHandler for GamepadOutput { } }); - let lx = (match voltex_state.laser[0] || (self.use_air && flat_controller_state[34]) { - true => -30000, - false => 0, - } + match voltex_state.laser[1] || (self.use_air && flat_controller_state[35]) { - true => 30000, - false => 0, - }); + let lx = self.left_wind.update( + voltex_state.laser[0] || (self.use_air && flat_controller_state[32]), + voltex_state.laser[1] + || (self.use_air && (flat_controller_state[33] || flat_controller_state[34])), + ) * 20000; - let rx = (match voltex_state.laser[2] || (self.use_air && flat_controller_state[36]) { - true => -30000, - false => 0, - } + match voltex_state.laser[3] || (self.use_air && flat_controller_state[37]) { - true => 30000, - false => 0, - }); + let rx = self.right_wind.update( + voltex_state.laser[2] + || (self.use_air && (flat_controller_state[35] || flat_controller_state[36])), + voltex_state.laser[3] || (self.use_air && flat_controller_state[37]), + ) * 20000; let mut dirty = false; if self.gamepad.buttons.raw != buttons { diff --git a/src-tauri/src/slider_io/keyboard.rs b/src-tauri/src/slider_io/keyboard.rs index d79c560..fb269aa 100644 --- a/src-tauri/src/slider_io/keyboard.rs +++ b/src-tauri/src/slider_io/keyboard.rs @@ -60,6 +60,24 @@ const VOLTEX_KB_MAP: [usize; 41] = [ 0x31, 0x1b, 0x0d, // 1, VK_ESCAPE, VK_RETURN ]; +#[rustfmt::skip] +const VOLTEX_KB_MAP_NEARDAYO: [usize; 41] = [ + 0x57, 0x57, 0x57, 0x57, // W + 0x45, 0x45, 0x45, 0x45, // E + 0x43, 0x44, + 0x43, 0x44, + 0x43, 0x46, // D + 0x43, 0x46, // C // F + 0x4d, 0x4a, // M // J + 0x4d, 0x4a, // K + 0x4d, 0x4b, + 0x4d, 0x4b, + 0x4f, 0x4f, 0x4f, 0x4f, // O + 0x50, 0x50, 0x50, 0x50, // P + 0x57, 0x45, 0x45, 0x4f, 0x4f, 0x50, // Disabled + 0x31, 0x1b, 0x0d, // 1, VK_ESCAPE, VK_RETURN +]; + pub struct KeyboardOutput { ground_to_idx: [usize; 41], idx_to_keycode: [u16; 41], @@ -78,6 +96,7 @@ impl KeyboardOutput { KeyboardLayout::Yuancon => &YUANCON_KB_MAP, KeyboardLayout::Deemo => &DEEMO_KB_MAP, KeyboardLayout::Voltex => &VOLTEX_KB_MAP, + KeyboardLayout::Neardayo => &VOLTEX_KB_MAP_NEARDAYO, }; let mut ground_to_idx = [0 as usize; 41]; diff --git a/src-tauri/src/slider_io/led.rs b/src-tauri/src/slider_io/led.rs index 4c8ba69..bc541e5 100644 --- a/src-tauri/src/slider_io/led.rs +++ b/src-tauri/src/slider_io/led.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use log::{error, info}; use palette::{FromColor, Hsv, Srgb}; use serialport::{ClearBuffer, SerialPort}; @@ -5,19 +6,22 @@ use std::{ ops::DerefMut, time::{Duration, Instant}, }; +use tokio::time::{interval, Interval}; use crate::slider_io::{ config::{LedMode, ReactiveLayout}, controller_state::{FullState, LedState}, utils::Buffer, voltex::VoltexState, - worker::ThreadJob, + worker::AsyncJob, }; pub struct LedJob { state: FullState, mode: LedMode, serial_port: Option>, + started: Instant, + timer: Interval, } impl LedJob { @@ -26,6 +30,8 @@ impl LedJob { state: state.clone(), mode: mode.clone(), serial_port: None, + started: Instant::now(), + timer: interval(Duration::from_micros(33333)), } } @@ -66,29 +72,29 @@ impl LedJob { led_state.led_state.fill(0); // Fixed - led_state.paint(3, &[0, 0, 64]); + led_state.paint(3, &[10, 100, 180]); for idx in 0..5 { led_state.paint(7 + idx * 4, &[64, 64, 64]); } - led_state.paint(27, &[64, 0, 0]); + led_state.paint(27, &[180, 10, 110]); let voltex_state = VoltexState::from_flat(flat_controller_state); // Left laser for (idx, state) in voltex_state.laser[0..2].iter().enumerate() { if *state { - led_state.paint(0 + idx * 4, &[0, 0, 255]); - led_state.paint(1 + idx * 4, &[0, 0, 255]); - led_state.paint(2 + idx * 4, &[0, 0, 255]); + led_state.paint(0 + idx * 4, &[70, 230, 250]); + led_state.paint(1 + idx * 4, &[70, 230, 250]); + led_state.paint(2 + idx * 4, &[70, 230, 250]); } } // Right laser for (idx, state) in voltex_state.laser[2..4].iter().enumerate() { if *state { - led_state.paint(24 + idx * 4, &[255, 0, 0]); - led_state.paint(25 + idx * 4, &[255, 0, 0]); - led_state.paint(26 + idx * 4, &[255, 0, 0]); + led_state.paint(24 + idx * 4, &[250, 60, 200]); + led_state.paint(25 + idx * 4, &[255, 60, 200]); + led_state.paint(26 + idx * 4, &[255, 60, 200]); } } @@ -103,17 +109,20 @@ impl LedJob { // Fx for (idx, state) in voltex_state.fx.iter().enumerate() { if *state { - led_state.paint(9 + idx * 8, &[255, 0, 0]); - led_state.paint(11 + idx * 8, &[255, 0, 0]); - led_state.paint(13 + idx * 8, &[255, 0, 0]); + led_state.paint(9 + idx * 8, &[250, 100, 30]); + led_state.paint(11 + idx * 8, &[250, 100, 30]); + led_state.paint(13 + idx * 8, &[250, 100, 30]); } } } } } LedMode::Attract => { - let now = Instant::now(); - let theta = (now - led_state.start).div_duration_f64(Duration::from_secs(4)) % 1.0; + let theta = self + .started + .elapsed() + .div_duration_f64(Duration::from_secs(4)) + % 1.0; for idx in 0..31 { let slice_theta = (&theta + (idx as f64) / 32.0) % 1.0; let color = Srgb::from_color(Hsv::new(slice_theta * 360.0, 1.0, 1.0)).into_format::(); @@ -147,8 +156,9 @@ impl LedJob { } } -impl ThreadJob for LedJob { - fn setup(&mut self) -> bool { +#[async_trait] +impl AsyncJob for LedJob { + async fn setup(&mut self) -> bool { match &self.mode { LedMode::Serial { port } => { info!( @@ -173,14 +183,14 @@ impl ThreadJob for LedJob { } } - fn tick(&mut self) -> bool { + async fn tick(&mut self) -> bool { let mut flat_controller_state: Option> = None; let mut serial_buffer: Option = None; // Do the IO here match self.mode { LedMode::Reactive { sensitivity, .. } => { - let controller_state_handle = self.state.controller_state.lock().unwrap(); + let controller_state_handle = self.state.controller_state.lock(); flat_controller_state = Some(controller_state_handle.flat(&sensitivity)); } LedMode::Serial { .. } => { @@ -209,7 +219,7 @@ impl ThreadJob for LedJob { // Then calculate and transfer { - let mut led_state_handle = self.state.led_state.lock().unwrap(); + let mut led_state_handle = self.state.led_state.lock(); self.calc_lights( flat_controller_state.as_ref(), serial_buffer.as_ref(), @@ -217,10 +227,9 @@ impl ThreadJob for LedJob { ); } // thread::sleep(Duration::from_millis(30)); - spin_sleep::sleep(Duration::from_micros(33333)); + // spin_sleep::sleep(Duration::from_micros(33333)); + self.timer.tick().await; true } - - fn teardown(&mut self) {} } diff --git a/src-tauri/src/slider_io/manager.rs b/src-tauri/src/slider_io/manager.rs index b80c364..be45459 100644 --- a/src-tauri/src/slider_io/manager.rs +++ b/src-tauri/src/slider_io/manager.rs @@ -1,6 +1,7 @@ use log::info; +use parking_lot::Mutex; use std::{ - sync::{Arc, Mutex}, + sync::Arc, thread::{self, JoinHandle}, }; use tokio::{ @@ -34,7 +35,7 @@ impl Manager { let join_handle = thread::spawn(move || { info!("Manager thread started"); let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(2) + .worker_threads(4) .enable_all() .build() .unwrap(); @@ -47,18 +48,18 @@ impl Manager { match rx_config.recv().await { Some(config) => { info!("Rebuilding context"); - let mut context_handle = context_cloned.lock().unwrap(); + let mut context_handle = context_cloned.lock(); context_handle.take(); let new_context = Context::new(config); let new_state = new_context.clone_state(); context_handle.replace(new_context); - let mut state_handle = state_cloned.lock().unwrap(); + let mut state_handle = state_cloned.lock(); state_handle.replace(new_state); }, None => { - let mut context_handle = context_cloned.lock().unwrap(); + let mut context_handle = context_cloned.lock(); context_handle.take(); } } @@ -83,12 +84,12 @@ impl Manager { } pub fn try_get_state(&self) -> Option { - let state_handle = self.state.lock().unwrap(); + let state_handle = self.state.lock(); state_handle.as_ref().map(|x| x.clone()) } pub fn get_timer_state(&self) -> String { - let context_handle = self.context.lock().unwrap(); + let context_handle = self.context.lock(); context_handle .as_ref() .map(|context| context.timer_state()) diff --git a/src-tauri/src/slider_io/output.rs b/src-tauri/src/slider_io/output.rs index 1a9f4fb..520870e 100644 --- a/src-tauri/src/slider_io/output.rs +++ b/src-tauri/src/slider_io/output.rs @@ -1,9 +1,11 @@ +use async_trait::async_trait; use log::error; use std::time::Duration; +use tokio::time::{interval, Interval}; use crate::slider_io::{ config::OutputMode, controller_state::FullState, gamepad::GamepadOutput, - keyboard::KeyboardOutput, worker::ThreadJob, + keyboard::KeyboardOutput, worker::AsyncJob, }; pub trait OutputHandler: Send { @@ -14,9 +16,9 @@ pub trait OutputHandler: Send { pub struct OutputJob { state: FullState, mode: OutputMode, - t: u64, sensitivity: u8, handler: Option>, + timer: Interval, } impl OutputJob { @@ -24,24 +26,25 @@ impl OutputJob { Self { state: state.clone(), mode: mode.clone(), - t: 0, sensitivity: 0, handler: None, + timer: interval(Duration::MAX), } } } -impl ThreadJob for OutputJob { - fn setup(&mut self) -> bool { +#[async_trait] +impl AsyncJob for OutputJob { + async fn setup(&mut self) -> bool { match self.mode { OutputMode::Keyboard { layout, polling, sensitivity, } => { - self.t = polling.to_t_u64(); self.sensitivity = sensitivity; self.handler = Some(Box::new(KeyboardOutput::new(layout.clone()))); + self.timer = interval(Duration::from_micros(polling.to_t_u64())); true } @@ -50,9 +53,10 @@ impl ThreadJob for OutputJob { polling, sensitivity, } => { - self.t = polling.to_t_u64(); self.sensitivity = sensitivity; let handler = GamepadOutput::new(layout.clone()); + self.timer = interval(Duration::from_micros(polling.to_t_u64())); + match handler { Some(handler) => { self.handler = Some(Box::new(handler)); @@ -68,22 +72,24 @@ impl ThreadJob for OutputJob { } } - fn tick(&mut self) -> bool { + async fn tick(&mut self) -> bool { let flat_controller_state: Vec; { - let controller_state_handle = self.state.controller_state.lock().unwrap(); + let controller_state_handle = self.state.controller_state.lock(); flat_controller_state = controller_state_handle.flat(&self.sensitivity); } if let Some(handler) = self.handler.as_mut() { handler.tick(&flat_controller_state); } - spin_sleep::sleep(Duration::from_micros(self.t)); + self.timer.tick().await; true } +} - fn teardown(&mut self) { +impl Drop for OutputJob { + fn drop(&mut self) { if let Some(handler) = self.handler.as_mut() { handler.reset(); } diff --git a/src-tauri/src/slider_io/worker.rs b/src-tauri/src/slider_io/worker.rs index 2a53d6a..c508c8a 100644 --- a/src-tauri/src/slider_io/worker.rs +++ b/src-tauri/src/slider_io/worker.rs @@ -16,7 +16,6 @@ use crate::slider_io::utils::LoopTimer; pub trait ThreadJob: Send { fn setup(&mut self) -> bool; fn tick(&mut self) -> bool; - fn teardown(&mut self); } pub struct ThreadWorker { @@ -46,8 +45,7 @@ impl ThreadWorker { timer.tick(); } } - info!("Thread worker stopping internal {}", name); - job.teardown(); + info!("Thread worker received stop {}", name); })), stop_signal, } @@ -56,32 +54,88 @@ impl ThreadWorker { impl Drop for ThreadWorker { fn drop(&mut self) { - info!("Thread worker stopping {}", self.name); + info!("Thread worker stopping gracefully {}", self.name); self.stop_signal.store(true, Ordering::SeqCst); if let Some(thread) = self.thread.take() { thread.join().ok(); }; + + info!("Thread worker stopped {}", self.name); } } #[async_trait] pub trait AsyncJob: Send + 'static { - async fn run + Send>(self, stop_signal: F); + async fn setup(&mut self) -> bool; + async fn tick(&mut self) -> bool; } pub struct AsyncWorker { name: &'static str, task: Option>, - stop_signal: Option>, + stop_signal: Arc, } impl AsyncWorker { - pub fn new(name: &'static str, job: T) -> AsyncWorker + pub fn new(name: &'static str, mut job: T, mut timer: LoopTimer) -> Self where T: AsyncJob, { - info!("Async worker starting {}", name); + let stop_signal = Arc::new(AtomicBool::new(false)); + + let stop_signal_clone = Arc::clone(&stop_signal); + let task = tokio::spawn(async move { + let setup_res = job.setup().await; + stop_signal_clone.store(!setup_res, Ordering::SeqCst); + + loop { + if stop_signal_clone.load(Ordering::SeqCst) { + break; + } + if job.tick().await { + timer.tick(); + } + } + info!("Async worker received stop {}", name); + }); + + Self { + name, + task: Some(task), + stop_signal, + } + } +} + +impl Drop for AsyncWorker { + fn drop(&mut self) { + info!("Async worker stopping gracefully {}", self.name); + + self.stop_signal.store(true, Ordering::SeqCst); + drop(self.task.take()); + + info!("Async worker stopped {}", self.name); + } +} + +#[async_trait] +pub trait AsyncHaltableJob: Send + 'static { + async fn run + Send>(self, stop_signal: F); +} + +pub struct AsyncHaltableWorker { + name: &'static str, + task: Option>, + stop_signal: Option>, +} + +impl AsyncHaltableWorker { + pub fn new(name: &'static str, job: T) -> Self + where + T: AsyncHaltableJob, + { + info!("AsyncHaltable worker starting {}", name); let (send_stop, recv_stop) = oneshot::channel::<()>(); @@ -89,11 +143,12 @@ impl AsyncWorker { job .run(async move { recv_stop.await.ok(); + info!("AsyncHaltable worker received stop {}", name); }) .await; }); - AsyncWorker { + Self { name, task: Some(task), stop_signal: Some(send_stop), @@ -101,13 +156,14 @@ impl AsyncWorker { } } -impl Drop for AsyncWorker { +impl Drop for AsyncHaltableWorker { fn drop(&mut self) { - info!("Async worker stopping {}", self.name); + info!("AsyncHaltable worker stopping gracefully {}", self.name); if let Some(stop_signal) = self.stop_signal.take() { stop_signal.send(()).ok(); } self.task.take(); + info!("AsyncHaltable worker stopped {}", self.name); } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 463e7f0..94d9b22 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "slidershim", - "version": "0.1.3" + "version": "0.1.4" }, "build": { "distDir": "../public", diff --git a/src/App.svelte b/src/App.svelte index 0453235..3ae2768 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -179,6 +179,7 @@ {/if} +
Output Mode
@@ -189,6 +190,7 @@ + 100 Hz - +
@@ -263,6 +265,7 @@ {/if} +
LED Mode