pull/13/head
4yn 2022-02-07 10:10:36 +08:00
parent a5c89f00cb
commit 98509250d6
14 changed files with 199 additions and 53 deletions

View File

@ -33,7 +33,7 @@ body {
background: #333; background: #333;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@ -42,8 +42,13 @@ body {
height: 2rem; height: 2rem;
} }
.titlebar-front {
background: #0000;
}
.header-icon { .header-icon {
max-height: 100%; max-height: 100%;
flex: 0 0 auto;
} }
.header-icon img { .header-icon img {
@ -54,6 +59,15 @@ body {
.header { .header {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 500; font-weight: 500;
flex: 0 0 auto;
}
.header-space {
flex: 1 0 auto;
}
.header-timer {
flex: 0 0 auto;
} }
/* main */ /* main */

View File

@ -13,12 +13,5 @@
<script defer src="/build/bundle.js"></script> <script defer src="/build/bundle.js"></script>
</head> </head>
<body> <body></body>
<div data-tauri-drag-region class="titlebar">
<div data-tauri-drag-region class="header-icon">
<img src="/icon.png" />
</div>
<div data-tauri-drag-region class="header">&nbsp;slidershim</div>
</div>
</body>
</html> </html>

20
src-tauri/Cargo.lock generated
View File

@ -115,6 +115,12 @@ dependencies = [
"system-deps 3.2.0", "system-deps 3.2.0",
] ]
[[package]]
name = "atomic_float"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d"
[[package]] [[package]]
name = "attohttpc" name = "attohttpc"
version = "0.17.0" version = "0.17.0"
@ -2933,9 +2939,10 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]] [[package]]
name = "slidershim" name = "slidershim"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"atomic_float",
"base64", "base64",
"directories", "directories",
"env_logger", "env_logger",
@ -2954,6 +2961,7 @@ dependencies = [
"serde_json", "serde_json",
"serialport", "serialport",
"simple-logging", "simple-logging",
"spin_sleep",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tokio", "tokio",
@ -2995,6 +3003,16 @@ dependencies = [
"system-deps 1.3.2", "system-deps 1.3.2",
] ]
[[package]]
name = "spin_sleep"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a98101bdc3833e192713c2af0b0dd2614f50d1cf1f7a97c5221b7aac052acc7"
dependencies = [
"once_cell",
"winapi",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "slidershim" name = "slidershim"
version = "0.1.2" version = "0.1.3"
description = "slidershim" description = "slidershim"
authors = ["4yn"] authors = ["4yn"]
license = "" license = ""
@ -21,8 +21,11 @@ log = "0.4.14"
env_logger = "0.9.0" env_logger = "0.9.0"
simple-logging = "2.0.2" simple-logging = "2.0.2"
open = "2.0.2" open = "2.0.2"
atomic_float = "0.1.0"
spin_sleep = "1.0.0"
tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] } tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] }
futures = "0.3.19" futures = "0.3.19"
futures-util = "0.3.19" futures-util = "0.3.19"
async-trait = "0.1.52" async-trait = "0.1.52"

View File

@ -140,9 +140,12 @@ fn main() {
let manager_clone = Arc::clone(&manager); let manager_clone = Arc::clone(&manager);
app.listen_global("queryState", move |_| { app.listen_global("queryState", move |_| {
// app_handle.emit_all("showState", "@@@"); // app_handle.emit_all("showState", "@@@");
let snapshot = { let (snapshot, timer) = {
let manager_handle = manager_clone.lock().unwrap(); let manager_handle = manager_clone.lock().unwrap();
manager_handle.try_get_state().map(|x| x.snapshot()) (
manager_handle.try_get_state().map(|x| x.snapshot()),
manager_handle.get_timer_state(),
)
}; };
match snapshot { match snapshot {
Some(snapshot) => { Some(snapshot) => {
@ -150,6 +153,8 @@ fn main() {
} }
_ => {} _ => {}
} }
app_handle.emit_all("showTimerState", timer).ok();
}); });
// Config set event // Config set event

View File

@ -1,4 +1,6 @@
use atomic_float::AtomicF64;
use log::info; use log::info;
use std::sync::{atomic::Ordering, Arc};
use crate::slider_io::{ use crate::slider_io::{
brokenithm::BrokenithmJob, brokenithm::BrokenithmJob,
@ -7,6 +9,7 @@ use crate::slider_io::{
device::HidDeviceJob, device::HidDeviceJob,
led::LedJob, led::LedJob,
output::OutputJob, output::OutputJob,
utils::LoopTimer,
worker::{AsyncWorker, ThreadWorker}, worker::{AsyncWorker, ThreadWorker},
}; };
@ -18,6 +21,7 @@ pub struct Context {
brokenithm_worker: Option<AsyncWorker>, brokenithm_worker: Option<AsyncWorker>,
output_worker: Option<ThreadWorker>, output_worker: Option<ThreadWorker>,
led_worker: Option<ThreadWorker>, led_worker: Option<ThreadWorker>,
timers: Vec<(&'static str, Arc<AtomicF64>)>,
} }
impl Context { impl Context {
@ -28,6 +32,7 @@ impl Context {
info!("LED config {:?}", config.led_mode); info!("LED config {:?}", config.led_mode);
let state = FullState::new(); let state = FullState::new();
let mut timers = vec![];
let (device_worker, brokenithm_worker) = match &config.device_mode { let (device_worker, brokenithm_worker) = match &config.device_mode {
DeviceMode::None => (None, None), DeviceMode::None => (None, None),
@ -42,26 +47,41 @@ impl Context {
)), )),
), ),
_ => ( _ => (
Some(ThreadWorker::new( {
"device", let timer = LoopTimer::new();
HidDeviceJob::from_config(&state, &config.device_mode), timers.push(("d", timer.fork()));
)), Some(ThreadWorker::new(
"device",
HidDeviceJob::from_config(&state, &config.device_mode),
timer,
))
},
None, None,
), ),
}; };
let output_worker = match &config.output_mode { let output_worker = match &config.output_mode {
OutputMode::None => None, OutputMode::None => None,
_ => Some(ThreadWorker::new( _ => {
"output", let timer = LoopTimer::new();
OutputJob::new(&state, &config.output_mode), timers.push(("o", timer.fork()));
)), Some(ThreadWorker::new(
"output",
OutputJob::new(&state, &config.output_mode),
timer,
))
}
}; };
let led_worker = match &config.led_mode { let led_worker = match &config.led_mode {
LedMode::None => None, LedMode::None => None,
_ => Some(ThreadWorker::new( _ => {
"led", let timer = LoopTimer::new();
LedJob::new(&state, &config.led_mode), timers.push(("l", timer.fork()));
)), Some(ThreadWorker::new(
"led",
LedJob::new(&state, &config.led_mode),
timer,
))
}
}; };
Self { Self {
@ -71,10 +91,20 @@ impl Context {
brokenithm_worker, brokenithm_worker,
output_worker, output_worker,
led_worker, led_worker,
timers,
} }
} }
pub fn clone_state(&self) -> FullState { pub fn clone_state(&self) -> FullState {
self.state.clone() self.state.clone()
} }
pub fn timer_state(&self) -> String {
self
.timers
.iter()
.map(|(s, f)| format!("{}:{:.1}/s", s, f.load(Ordering::SeqCst)))
.collect::<Vec<String>>()
.join(" ")
}
} }

View File

@ -101,9 +101,9 @@ impl HidDeviceJob {
.take(31) .take(31)
.zip(led_state.led_state.chunks(3).rev()) .zip(led_state.led_state.chunks(3).rev())
{ {
buf_chunk[0] = state_chunk[2]; buf_chunk[0] = state_chunk[1];
buf_chunk[1] = state_chunk[0]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[1]; buf_chunk[2] = state_chunk[2];
} }
buf.data[96..240].fill(0); buf.data[96..240].fill(0);
}, },
@ -139,9 +139,9 @@ impl HidDeviceJob {
.take(31) .take(31)
.zip(led_state.led_state.chunks(3).rev()) .zip(led_state.led_state.chunks(3).rev())
{ {
buf_chunk[0] = state_chunk[2]; buf_chunk[0] = state_chunk[1];
buf_chunk[1] = state_chunk[0]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[1]; buf_chunk[2] = state_chunk[2];
} }
buf.data[96..240].fill(0); buf.data[96..240].fill(0);
}, },
@ -224,9 +224,10 @@ impl ThreadJob for HidDeviceJob {
} }
} }
fn tick(&mut self) { fn tick(&mut self) -> bool {
// Input loop // Input loop
let handle = self.handle.as_mut().unwrap(); let handle = self.handle.as_mut().unwrap();
let mut work = false;
{ {
let res = handle let res = handle
@ -239,6 +240,7 @@ impl ThreadJob for HidDeviceJob {
self.read_buf.len = res; self.read_buf.len = res;
// debug!("{:?}", self.read_buf.slice()); // debug!("{:?}", self.read_buf.slice());
if self.read_buf.len != 0 { if self.read_buf.len != 0 {
work = true;
let mut controller_state_handle = self.state.controller_state.lock().unwrap(); let mut controller_state_handle = self.state.controller_state.lock().unwrap();
(self.read_callback)(&self.read_buf, controller_state_handle.deref_mut()); (self.read_callback)(&self.read_buf, controller_state_handle.deref_mut());
} }
@ -267,12 +269,13 @@ impl ThreadJob for HidDeviceJob {
}) })
.unwrap_or(0); .unwrap_or(0);
if res == self.led_buf.len + 1 { if res == self.led_buf.len + 1 {
work = true;
self.led_buf.len = 0; self.led_buf.len = 0;
} }
} }
} }
// thread::sleep(Duration::from_millis(10)); work
} }
fn teardown(&mut self) { fn teardown(&mut self) {

View File

@ -174,7 +174,7 @@ impl ThreadJob for LedJob {
} }
} }
fn tick(&mut self) { fn tick(&mut self) -> bool {
let mut flat_controller_state: Option<Vec<bool>> = None; let mut flat_controller_state: Option<Vec<bool>> = None;
let mut serial_buffer: Option<Buffer> = None; let mut serial_buffer: Option<Buffer> = None;
@ -217,7 +217,10 @@ impl ThreadJob for LedJob {
led_state_handle.deref_mut(), led_state_handle.deref_mut(),
); );
} }
thread::sleep(Duration::from_millis(30)); // thread::sleep(Duration::from_millis(30));
spin_sleep::sleep(Duration::from_millis(30));
true
} }
fn teardown(&mut self) {} fn teardown(&mut self) {}

View File

@ -14,6 +14,7 @@ use super::controller_state::FullState;
pub struct Manager { pub struct Manager {
state: Arc<Mutex<Option<FullState>>>, state: Arc<Mutex<Option<FullState>>>,
context: Arc<Mutex<Option<Context>>>,
join_handle: Option<JoinHandle<()>>, join_handle: Option<JoinHandle<()>>,
tx_config: mpsc::UnboundedSender<Config>, tx_config: mpsc::UnboundedSender<Config>,
tx_stop: Option<oneshot::Sender<()>>, tx_stop: Option<oneshot::Sender<()>>,
@ -70,6 +71,7 @@ impl Manager {
Self { Self {
state, state,
context,
join_handle: Some(join_handle), join_handle: Some(join_handle),
tx_config, tx_config,
tx_stop: Some(tx_stop), tx_stop: Some(tx_stop),
@ -84,6 +86,14 @@ impl Manager {
let state_handle = self.state.lock().unwrap(); let state_handle = self.state.lock().unwrap();
state_handle.as_ref().map(|x| x.clone()) state_handle.as_ref().map(|x| x.clone())
} }
pub fn get_timer_state(&self) -> String {
let context_handle = self.context.lock().unwrap();
context_handle
.as_ref()
.map(|context| context.timer_state())
.unwrap_or("".to_string())
}
} }
impl Drop for Manager { impl Drop for Manager {

View File

@ -50,7 +50,7 @@ impl ThreadJob for OutputJob {
true true
} }
fn tick(&mut self) { fn tick(&mut self) -> bool {
let flat_controller_state: Vec<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().unwrap();
@ -58,7 +58,10 @@ impl ThreadJob for OutputJob {
} }
self.handler.tick(&flat_controller_state); self.handler.tick(&flat_controller_state);
thread::sleep(Duration::from_millis(self.t)); // thread::sleep(Duration::from_millis(self.t));
spin_sleep::sleep(Duration::from_millis(self.t));
true
} }
fn teardown(&mut self) { fn teardown(&mut self) {

View File

@ -1,4 +1,10 @@
use std::{error::Error, fmt}; use atomic_float::AtomicF64;
use std::{
error::Error,
fmt,
sync::{atomic::Ordering, Arc},
time::{Duration, Instant},
};
pub struct Buffer { pub struct Buffer {
pub data: [u8; 256], pub data: [u8; 256],
@ -44,3 +50,45 @@ pub fn list_ips() -> Result<Vec<String>, Box<dyn Error>> {
Ok(ips) Ok(ips)
} }
pub struct LoopTimer {
cap: usize,
cur: usize,
buf: Vec<Instant>,
freq: Arc<AtomicF64>,
}
impl LoopTimer {
pub fn new() -> Self {
Self {
cap: 100,
cur: 0,
buf: vec![Instant::now() - Duration::from_secs(10); 100],
freq: Arc::new(AtomicF64::new(0.0)),
}
}
pub fn tick(&mut self) {
let last = self.buf[self.cur];
let now = Instant::now();
self.buf[self.cur] = now;
let delta = (now - last) / 100 + Duration::from_micros(1);
let freq = Duration::from_millis(1000)
.div_duration_f64(delta)
.clamp(0.0, 9999.0);
self.freq.store(freq, Ordering::SeqCst);
self.cur = match self.cur + 1 {
cur if cur == self.cap => 0,
cur => cur,
}
}
// pub fn reset(&mut self) {
// self.buf = vec![Instant::now(); 100];
// }
pub fn fork(&self) -> Arc<AtomicF64> {
Arc::clone(&self.freq)
}
}

View File

@ -11,9 +11,11 @@ use std::{
use tokio::{sync::oneshot, task}; use tokio::{sync::oneshot, task};
use crate::slider_io::utils::LoopTimer;
pub trait ThreadJob: Send { pub trait ThreadJob: Send {
fn setup(&mut self) -> bool; fn setup(&mut self) -> bool;
fn tick(&mut self); fn tick(&mut self) -> bool;
fn teardown(&mut self); fn teardown(&mut self);
} }
@ -24,7 +26,7 @@ pub struct ThreadWorker {
} }
impl ThreadWorker { impl ThreadWorker {
pub fn new<T: 'static + ThreadJob>(name: &'static str, mut job: T) -> Self { pub fn new<T: 'static + ThreadJob>(name: &'static str, mut job: T, mut timer: LoopTimer) -> Self {
info!("Thread worker starting {}", name); info!("Thread worker starting {}", name);
let stop_signal = Arc::new(AtomicBool::new(false)); let stop_signal = Arc::new(AtomicBool::new(false));
@ -40,7 +42,9 @@ impl ThreadWorker {
if stop_signal_clone.load(Ordering::SeqCst) { if stop_signal_clone.load(Ordering::SeqCst) {
break; break;
} }
job.tick(); if job.tick() {
timer.tick();
}
} }
info!("Thread worker stopping internal {}", name); info!("Thread worker stopping internal {}", name);
job.teardown(); job.teardown();

View File

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

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { emit, listen } from "@tauri-apps/api/event"; import { emit, listen } from "@tauri-apps/api/event";
import { open } from "@tauri-apps/api/shell"; import { getVersion } from "@tauri-apps/api/app";
import Link from "./Link.svelte"; import Link from "./Link.svelte";
import Preview from "./Preview.svelte"; import Preview from "./Preview.svelte";
@ -24,11 +24,12 @@
} }
// let debugstr = ""; // let debugstr = "";
let versionString = "";
let ips: Array<string> = []; let ips: Array<string> = [];
let polling = null; let polling = null;
let tick = 0; let tick = 0;
let previewData = Array(131).fill(0); let previewData = Array(131).fill(0);
let timerData = "";
function updatePolling(enabled) { function updatePolling(enabled) {
if (!!polling) { if (!!polling) {
@ -65,6 +66,9 @@
await listen("showState", (event) => { await listen("showState", (event) => {
previewData = event.payload as any; previewData = event.payload as any;
}); });
await listen("showTimerState", (event) => {
timerData = event.payload as string;
});
await listen("listIps", (event) => { await listen("listIps", (event) => {
ips = (event.payload as Array<string>).filter( ips = (event.payload as Array<string>).filter(
@ -83,6 +87,8 @@
console.log("ackHide"); console.log("ackHide");
updatePolling(false); updatePolling(false);
}); });
versionString = ` ${await getVersion()}`;
}); });
// Emit events // Emit events
@ -124,16 +130,20 @@
} }
</script> </script>
<div class="titlebar">
<div class="header-icon">
<img src="/icon.png" />
</div>
<div class="header">
&nbsp;slidershim{versionString}
</div>
<div class="header-space" />
<div class="header-timer">
{timerData}
</div>
</div>
<div data-tauri-drag-region class="titlebar titlebar-front" />
<main class="main"> <main class="main">
<!-- <div class="row titlebar" data-tauri-drag-region> -->
<!-- <div class="header"> -->
<!-- slidershim by @4yn -->
<!-- slidershim -->
<!-- </div> -->
<!-- </div> -->
<!-- <div>
{debugstr}
</div> -->
<div class="row"> <div class="row">
<Preview data={previewData} /> <Preview data={previewData} />
</div> </div>
@ -148,7 +158,9 @@
<option value="brokenithm">Brokenithm</option> <option value="brokenithm">Brokenithm</option>
<option value="brokenithm-led">Brokenithm + Led</option> <option value="brokenithm-led">Brokenithm + Led</option>
<option value="brokenithm-ground">Brokenithm, Ground only</option> <option value="brokenithm-ground">Brokenithm, Ground only</option>
<option value="brokenithm-ground-led">Brokenithm + Led, Ground only</option> <option value="brokenithm-ground-led"
>Brokenithm + Led, Ground only</option
>
</select> </select>
</div> </div>
</div> </div>
@ -192,7 +204,7 @@
<option value="100">100 Hz</option> <option value="100">100 Hz</option>
<option value="330">330 Hz</option> <option value="330">330 Hz</option>
<option value="500">500 Hz</option> <option value="500">500 Hz</option>
<option value="1000">1000 Hz</option> <option value="1000">1000 Hz (Unstable, may use a lot of CPU)</option>
</select> </select>
</div> </div>
</div> </div>