Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
move throttle logic into rust
Browse files Browse the repository at this point in the history
  • Loading branch information
ruihe774 committed Feb 5, 2024
1 parent 45d1759 commit 2b2e663
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 53 deletions.
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ raw-window-handle = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_with = { version = "3.0", features = ["base64"] }
tauri = { version = "1.5.x", features = ["wry", "window-set-focus", "window-set-position", "window-set-size", "window-show", "window-hide", "system-tray"], default-features = false }
tokio = { version = "1.0", features = ["time"] }
windows-version = "0.1"

[dependencies.windows]
Expand Down
52 changes: 41 additions & 11 deletions src-tauri/src/monitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use std::ffi::OsStr;

use monitor::{Feature, Interface, Monitor};
use serde::{Deserialize, Serialize};
use tauri::async_runtime::RwLock;
use tauri::async_runtime::{Mutex, RwLock};
use tauri::State;
use tokio::time::{sleep, sleep_until, Instant, Duration};

use crate::util::JSResult;

#[derive(Debug)]
pub struct Monitors(RwLock<Vec<Monitor>>);
pub struct Monitors(RwLock<Vec<(Monitor, Mutex<Instant>)>>);

impl Monitors {
pub const fn new() -> Monitors {
Expand All @@ -20,8 +21,9 @@ impl Monitors {
pub async fn refresh_monitors(monitors: State<'_, Monitors>) -> JSResult<()> {
let mut monitors = monitors.0.write().await;
monitors.clear();
let stub_instant = Instant::now();
for monitor in monitor::get_monitors() {
monitors.push(monitor);
monitors.push((monitor, Mutex::const_new(stub_instant)));
}
Ok(())
}
Expand All @@ -31,15 +33,15 @@ pub async fn get_monitors(monitors: State<'_, Monitors>) -> JSResult<Vec<String>
let monitors = monitors.0.read().await;
Ok(monitors
.iter()
.map(|monitor| monitor.id.to_string_lossy().into_owned())
.map(|(monitor, _)| monitor.id.to_string_lossy().into_owned())
.collect())
}

fn get_monitor_by_id<'a>(monitors: &'a [Monitor], id: &'_ str) -> JSResult<&'a Monitor> {
fn get_monitor_by_id<'a>(monitors: &'a [(Monitor, Mutex<Instant>)], id: &'_ str) -> JSResult<&'a (Monitor, Mutex<Instant>)> {
let id_os: &OsStr = id.as_ref();
monitors
.iter()
.find(|monitor| monitor.id == id_os)
.find(|(monitor, _)| monitor.id == id_os)
.ok_or_else(|| format!("no such monitor: '{id}'").into())
}

Expand All @@ -49,7 +51,7 @@ pub async fn get_monitor_user_friendly_name(
id: String,
) -> JSResult<Option<String>> {
let monitors = monitors.0.read().await;
let monitor = get_monitor_by_id(&monitors, &id)?;
let monitor = &get_monitor_by_id(&monitors, &id)?.0;
Ok(monitor
.get_user_friendly_name()?
.map(|s| s.to_string_lossy().into_owned()))
Expand All @@ -74,20 +76,28 @@ pub struct Reply {
source: &'static str,
}

// TODO: move it into JS
const UPDATE_INTERVAL: Duration = Duration::from_millis(200);

#[tauri::command]
pub async fn get_monitor_feature(
monitors: State<'_, Monitors>,
id: String,
feature: String,
) -> JSResult<Reply> {
let monitors = monitors.0.read().await;
let monitor = get_monitor_by_id(&monitors, &id)?;
let (monitor, instant) = get_monitor_by_id(&monitors, &id)?;
let feature = feature_from_string(feature)?;

let mut instant = instant.lock().await;
sleep_until(*instant).await;
let monitor::Reply {
current,
maximum,
source,
} = monitor.get_feature(feature)?;
*instant = Instant::now() + UPDATE_INTERVAL;

Ok(Reply {
current,
maximum,
Expand All @@ -104,9 +114,29 @@ pub async fn set_monitor_feature(
id: String,
feature: String,
value: u32,
) -> JSResult<()> {
) -> JSResult<Reply> {
let monitors = monitors.0.read().await;
let monitor = get_monitor_by_id(&monitors, &id)?;
let (monitor, instant) = get_monitor_by_id(&monitors, &id)?;
let feature = feature_from_string(feature)?;
Ok(monitor.set_feature(feature, value)?)

let mut instant = instant.lock().await;
sleep_until(*instant).await;
monitor.set_feature(feature, value)?;

sleep(UPDATE_INTERVAL).await;
let monitor::Reply {
current,
maximum,
source,
} = monitor.get_feature(feature)?;
*instant = Instant::now() + UPDATE_INTERVAL;

Ok(Reply {
current,
maximum,
source: match source {
Interface::DDCCI => "ddcci",
Interface::IOCTL => "ioctl",
},
})
}
48 changes: 25 additions & 23 deletions src/components/FeatureSlider.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<script lang="ts">
import { defineComponent } from "vue";
import { clamp, debounce } from "lodash-es";
import { clamp } from "lodash-es";
import monitorManager from "../monitor";
import sheet from "../style.module.sass";
import settings from "../settings";
const iconMap: { [key: string]: string } = {
luminance: "\uE706",
Expand Down Expand Up @@ -31,9 +30,11 @@ export default defineComponent({
},
data(): {
input: number | null;
updating: Promise<void>;
} {
return {
input: null,
updating: Promise.resolve(),
};
},
computed: {
Expand All @@ -49,33 +50,14 @@ export default defineComponent({
icon() {
return iconMap[this.featureName];
},
update() {
const { updateInterval } = settings;
return debounce(
(id, name, value) => {
monitorManager.setFeature(id, name, value);
},
updateInterval,
{
leading: true,
maxWait: updateInterval,
},
);
},
sync() {
const { updateInterval } = settings;
return debounce(() => {
this.input = null;
}, updateInterval * 2);
},
},
methods: {
handleInput(event: Event) {
const e = event as InputEvent;
const target = e.target! as HTMLInputElement;
if (!e.isComposing && target.validity.valid) {
this.input = Number(target.value);
this.update(this.monitorId, this.featureName, this.input);
this.update();
}
},
handleWheel(event: Event) {
Expand All @@ -85,10 +67,30 @@ export default defineComponent({
const offset = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : -e.deltaY;
const current = Number(target.value);
this.input = clamp(Math.round(current + offset * 0.01), 0, this.maximum);
this.update(this.monitorId, this.featureName, this.input);
this.update();
this.sync();
}
},
update() {
this.updating = this.updating.then(() => {
if (this.input != null) {
try {
return monitorManager.setFeature(
this.monitorId,
this.featureName,
this.input,
);
} catch (e) {
console.error(e);
}
}
});
},
sync() {
this.updating = this.updating.then(() => {
this.input = null;
});
},
},
});
</script>
Expand Down
21 changes: 2 additions & 19 deletions src/monitor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { invoke } from "@tauri-apps/api";
import { reactive, toRaw, DeepReadonly } from "vue";
import settings from "./settings";

export interface Reply {
current: number;
Expand All @@ -19,12 +18,6 @@ export interface Monitor {
features: Feature[];
}

function timeout(millis: number): Promise<undefined> {
return new Promise((resolve) => {
setTimeout(resolve, millis);
});
}

export class Manager {
readonly monitors: DeepReadonly<Monitor[]> = reactive([]);
private refreshing = false;
Expand Down Expand Up @@ -66,11 +59,7 @@ export class Manager {
const featureNames = monitor.features.length
? monitor.features.map((feature) => feature.name)
: ["luminance", "contrast", "brightness", "volume", "powerstate"];
let first = true;
for (const name of featureNames) {
if (!first) {
await timeout(settings.updateInterval);
}
let value: Reply | undefined;
try {
value = await invoke<Reply>("get_monitor_feature", {
Expand All @@ -92,7 +81,6 @@ export class Manager {
} else if (idx != -1) {
monitor.features.splice(idx, 1);
}
first = false;
}
})(),
);
Expand Down Expand Up @@ -131,17 +119,12 @@ export class Manager {
async setFeature(id: string, name: string, value: number): Promise<void> {
const feature = this.getFeature(id, name) as Feature;
if (feature.value.current != value) {
await invoke<void>("set_monitor_feature", {
id,
feature: name,
value,
});
await timeout(settings.updateInterval);
Object.assign(
feature.value,
await invoke<Reply>("get_monitor_feature", {
await invoke<Reply>("set_monitor_feature", {
id,
feature: name,
value,
}),
);
}
Expand Down

0 comments on commit 2b2e663

Please sign in to comment.