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

Commit

Permalink
debounce in component side
Browse files Browse the repository at this point in the history
  • Loading branch information
ruihe774 committed Jan 3, 2024
1 parent 8612acf commit 29c3827
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 115 deletions.
61 changes: 40 additions & 21 deletions src/components/FeatureSlider.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import { defineComponent } from "vue";
import { clamp } from "lodash-es";
import { clamp, debounce } from "lodash-es";
import monitorManager from "../monitor";
import sheet from "../style.module.sass";
import settings from "../settings";
const iconMap = {
luminance: "\uE706",
Expand All @@ -28,13 +29,17 @@ export default defineComponent({
sheet,
};
},
data(): {
input: number | null;
} {
return {
input: null,
};
},
computed: {
feature() {
return monitorManager.getFeature(this.monitorId, this.featureName);
},
readonly() {
return this.feature.readonly;
},
current() {
return this.feature.value.current;
},
Expand All @@ -44,38 +49,52 @@ export default defineComponent({
icon() {
return (iconMap as { [key: string]: string })[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 (target.validity.valid) {
const value = Number(target.value);
monitorManager.updateFeature(this.monitorId, this.featureName, value);
if (!e.isComposing && target.validity.valid) {
this.input = Number(target.value);
this.update(this.monitorId, this.featureName, this.input);
}
},
handleWheel(event: Event) {
if (this.readonly) {
return;
}
const e = event as WheelEvent;
const target = e.currentTarget! as HTMLInputElement;
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL) {
const offset = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : -e.deltaY;
const current = Number(target.value);
monitorManager.updateFeature(
this.monitorId,
this.featureName,
clamp(Math.round(current + offset * 0.01), 0, this.maximum),
);
this.input = clamp(Math.round(current + offset * 0.01), 0, this.maximum);
this.update(this.monitorId, this.featureName, this.input);
this.sync();
}
},
},
});
</script>

<template>
<label :class="[sheet.flex, sheet.cozyLine, { [sheet.inactive]: readonly }]">
<label :class="[sheet.flex, sheet.cozyLine]">
<span :class="sheet.bigIcon" :aria-label="featureName">
{{ icon }}
</span>
Expand All @@ -84,24 +103,24 @@ export default defineComponent({
step="1"
min="0"
:max="maximum"
:value="current"
:disabled="readonly"
:value="input ?? current"
:class="[sheet.grow, sheet.slider]"
:style="`--slider-value: ${(current / maximum) * 100}%`"
@input="handleInput"
@wheel="handleWheel"
@change="sync"
@wheel.prevent="handleWheel"
/>
<input
type="number"
role="status"
step="1"
min="0"
:max="maximum"
:value="current"
:disabled="readonly"
:value="input ?? current"
:class="[sheet.borderlessNumber, sheet.titleFont, 'number']"
style="width: 1.7em"
@input="handleInput"
@change="sync"
@wheel.prevent="handleWheel"
/>
</label>
Expand Down
2 changes: 1 addition & 1 deletion src/components/MonitorItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default defineComponent({
},
methods: {
handlePowerOff() {
monitorManager.updateFeature(
monitorManager.setFeature(
this.monitorId,
"powerstate",
Math.min(settings.ddcPowerOffValue, this.powerState!.maximum),
Expand Down
129 changes: 37 additions & 92 deletions src/monitor.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { invoke } from "@tauri-apps/api";
import { reactive, watch, DeepReadonly } from "vue";
import { watchThrottled } from "./watchers";
import { reactive, toRaw, DeepReadonly } from "vue";
import settings from "./settings";

export interface Reply {
current: number;
maximum: number;
source: "ddcci" | "wmi";
}

export interface Feature {
name: string;
value: Reply;
syncing: boolean;
readonly: boolean;
written: boolean;
}

export interface Monitor {
Expand All @@ -28,27 +25,26 @@ function timeout(millis: number): Promise<undefined> {
});
}

const featureTestTime = 200;

export class Manager {
readonly monitors: DeepReadonly<Monitor[]> = reactive([]);
private refreshing: boolean = false;
private refreshing = false;

private async doRefresh(): Promise<void> {
const monitors = this.monitors as Monitor[];
await invoke("refresh_monitors");
const monitorIds = await invoke<string[]>("get_monitors");
const monitorMap = new Map(monitors.map((monitor) => [monitor.id, monitor]));
monitors.splice(0);
for (const id of monitorIds) {
monitors.push(
monitorMap.get(id) ?? {
id,
name: null,
features: [],
},
);
}
Object.assign(
monitors,
monitorIds.map(
(id) =>
monitorMap.get(id) ?? {
id,
name: null,
features: [],
},
),
);
const pool: Promise<void>[] = [];
for (const monitor of monitors) {
if (monitor.name == null) {
Expand All @@ -73,7 +69,7 @@ export class Manager {
let first = true;
for (const name of featureNames) {
if (!first) {
await timeout(featureTestTime);
await timeout(settings.updateInterval);
}
let value: Reply | undefined;
try {
Expand All @@ -86,16 +82,11 @@ export class Manager {
if (value) {
const item = monitor.features[idx];
if (item) {
if (!item.syncing) {
item.value = value;
}
item.value = value;
} else {
monitor.features.push({
name,
value,
syncing: false,
readonly: false,
written: false,
});
}
} else if (idx != -1) {
Expand All @@ -121,85 +112,39 @@ export class Manager {
}

getMonitor(id: string): DeepReadonly<Monitor> {
const monitor = this.monitors.find((monitor) => monitor.id == id);
if (monitor) {
return monitor;
const idx = toRaw(this.monitors).findIndex((monitor) => monitor.id == id);
if (idx == -1) {
throw new Error(`no such monitor: '${id}'`);
}
throw new Error(`no such monitor: '${id}'`);
return this.monitors[idx];
}

getFeature(id: string, name: string): DeepReadonly<Feature> {
const monitor = this.getMonitor(id);
const feature = monitor.features.find((f) => f.name == name);
if (feature) {
return feature;
const idx = toRaw(monitor.features).findIndex((f) => f.name == name);
if (idx == -1) {
throw new Error(`monitor '${id}' has no such feature: '${name}'`);
}
throw new Error(`monitor '${id}' has no such feature: '${name}'`);
return monitor.features[idx];
}

updateFeature(id: string, name: string, value: number): void {
async setFeature(id: string, name: string, value: number): Promise<void> {
const feature = this.getFeature(id, name) as Feature;
if (feature.value.current != value) {
feature.value.current = value;
feature.syncing = true;
await invoke<void>("set_monitor_feature", {
id,
feature: name,
value,
});
await timeout(settings.updateInterval);
feature.value = await invoke<Reply>("get_monitor_feature", {
id,
feature: name,
});
}
}
}

const monitorManager = new Manager();

watch(
() => settings.updateInterval,
(updateInterval, _old, onCleanup) => {
onCleanup(
watchThrottled(
monitorManager.monitors as Monitor[],
(monitors) => {
for (const monitor of monitors) {
for (const feature of monitor.features) {
if (feature.syncing) {
(async () => {
try {
feature.syncing = false;
if (!feature.written) {
feature.readonly = true;
}
await invoke("set_monitor_feature", {
id: monitor.id,
feature: feature.name,
value: feature.value.current,
});
if (!feature.written) {
await timeout(featureTestTime);
const value = await invoke<Reply>(
"get_monitor_feature",
{
id: monitor.id,
feature: feature.name,
},
);
if (value.current == feature.value.current) {
feature.written = true;
feature.readonly = false;
} else {
feature.value = value;
}
}
} catch (e) {
console.error(e);
feature.readonly = true;
monitorManager.refresh();
}
})();
}
}
}
},
{ throttle: updateInterval, trailing: true },
),
);
},
{ immediate: true },
);
const manager = new Manager();

export default monitorManager;
export default manager;
2 changes: 1 addition & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface Settings {
}

export default reactive<Settings>({
updateInterval: 500,
updateInterval: 200,
ddcPowerOffValue: 6,
writingMode: "horizontal-tb",
});

0 comments on commit 29c3827

Please sign in to comment.