1
0
mirror of https://github.com/4yn/slidershim.git synced 2024-12-18 07:05:52 +01:00

async output and led

This commit is contained in:
4yn 2022-02-09 14:00:36 +08:00
parent afa5a46c65
commit 2ae7a81f22
16 changed files with 393 additions and 168 deletions

80
src-tauri/Cargo.lock generated
View File

@ -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"

View File

@ -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" ]

View File

@ -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());
}
});

View File

@ -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<F: Future<Output = ()> + Send>(self, stop_signal: F) {
let state = self.state.clone();
let ground_only = self.ground_only;

View File

@ -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<Self> {
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<Self> {
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"

View File

@ -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<ThreadWorker>,
brokenithm_worker: Option<AsyncWorker>,
output_worker: Option<ThreadWorker>,
led_worker: Option<ThreadWorker>,
brokenithm_worker: Option<AsyncHaltableWorker>,
output_worker: Option<AsyncWorker>,
led_worker: Option<AsyncWorker>,
timers: Vec<(&'static str, Arc<AtomicF64>)>,
}
@ -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,

View File

@ -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<u8> {
let mut buf: Vec<u8> = 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);
};

View File

@ -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();
}

View File

@ -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<Client>,
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 {

View File

@ -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];

View File

@ -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<Box<dyn SerialPort>>,
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::<u8>();
@ -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<Vec<bool>> = None;
let mut serial_buffer: Option<Buffer> = 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) {}
}

View File

@ -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<FullState> {
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())

View File

@ -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<Box<dyn OutputHandler>>,
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<bool>;
{
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();
}

View File

@ -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<F: Future<Output = ()> + 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<task::JoinHandle<()>>,
stop_signal: Option<oneshot::Sender<()>>,
stop_signal: Arc<AtomicBool>,
}
impl AsyncWorker {
pub fn new<T>(name: &'static str, job: T) -> AsyncWorker
pub fn new<T>(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<F: Future<Output = ()> + Send>(self, stop_signal: F);
}
pub struct AsyncHaltableWorker {
name: &'static str,
task: Option<task::JoinHandle<()>>,
stop_signal: Option<oneshot::Sender<()>>,
}
impl AsyncHaltableWorker {
pub fn new<T>(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);
}
}

View File

@ -1,7 +1,7 @@
{
"package": {
"productName": "slidershim",
"version": "0.1.3"
"version": "0.1.4"
},
"build": {
"distDir": "../public",

View File

@ -179,6 +179,7 @@
</div>
</div>
{/if}
<div class="row">
<div class="label">Output Mode</div>
<div class="input">
@ -189,6 +190,7 @@
<option value="kb-32-yuancon">Keyboard 32-zone, Yuancon Layout</option>
<option value="kb-8-deemo">Keyboard 8-zone, Deemo Layout</option>
<option value="kb-voltex">Keyboard 10-zone, Voltex Layout</option>
<option value="kb-neardayo">Keyboard 10-zone, Neardayo Layout</option>
<option value="gamepad-voltex">XBOX 360 Gamepad, Voltex Layout</option>
<option value="gamepad-neardayo"
>XBOX 360 Gamepad, Neardayo Layout</option
@ -206,7 +208,7 @@
<option value="100">100 Hz</option>
<option value="250">250 Hz</option>
<option value="500">500 Hz</option>
<option value="1000">1000 Hz (Unstable, may use a lot of CPU)</option>
<option value="1000">1000 Hz</option>
</select>
</div>
</div>
@ -263,6 +265,7 @@
</div>
</div>
{/if}
<div class="row">
<div class="label">LED Mode</div>
<div class="input">