mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-22 01:42:47 +00:00
feat: wasm zero
This commit is contained in:
@@ -75,6 +75,7 @@ const config = {
|
||||
"light_mode",
|
||||
"palette",
|
||||
"translate",
|
||||
"smart_toy",
|
||||
"play_arrow",
|
||||
"extension",
|
||||
"upload_file",
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface CCOSKeyReleaseEvent {
|
||||
|
||||
export interface CCOSSerialEvent {
|
||||
type: "serial";
|
||||
data: number;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export type CCOSInEvent =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getMeta } from "$lib/meta/meta-storage";
|
||||
import { connectable, from, multicast, Subject } from "rxjs";
|
||||
import type { SerialPortLike } from "$lib/serial/device";
|
||||
import type {
|
||||
CCOSInEvent,
|
||||
CCOSInitEvent,
|
||||
CCOSKeyPressEvent,
|
||||
CCOSKeyReleaseEvent,
|
||||
@@ -8,7 +9,7 @@ import type {
|
||||
} from "./ccos-events";
|
||||
import { KEYCODE_TO_SCANCODE, SCANCODE_TO_KEYCODE } from "./ccos-interop";
|
||||
|
||||
const device = ".zero_wasm";
|
||||
const device = "zero_wasm";
|
||||
|
||||
class CCOSKeyboardEvent extends KeyboardEvent {
|
||||
constructor(...params: ConstructorParameters<typeof KeyboardEvent>) {
|
||||
@@ -22,14 +23,17 @@ const MASK_ALT = 0b0100_0100;
|
||||
const MASK_ALT_GRAPH = 0b0000_0100;
|
||||
const MASK_GUI = 0b1000_1000;
|
||||
|
||||
export class CCOS {
|
||||
export class CCOS implements SerialPortLike {
|
||||
private readonly currKeys = new Set<number>();
|
||||
|
||||
private readonly layout = new Map<string, string>();
|
||||
|
||||
private readonly worker = new Worker("/ccos-worker.js", { type: "module" });
|
||||
|
||||
private ready = false;
|
||||
private resolveReady!: () => void;
|
||||
private ready = new Promise<void>((resolve) => {
|
||||
this.resolveReady = resolve;
|
||||
});
|
||||
|
||||
private lastEvent?: KeyboardEvent;
|
||||
|
||||
@@ -109,33 +113,29 @@ export class CCOS {
|
||||
this.currKeys.delete(0);
|
||||
}
|
||||
|
||||
private outStream = new Subject<number>();
|
||||
private controller?: ReadableStreamDefaultController<Uint8Array>;
|
||||
|
||||
private readonly buffer: number[] = [];
|
||||
private readonly outStream = new WritableStream<number>({
|
||||
start(controller) {},
|
||||
});
|
||||
|
||||
readonly readable = connectable()
|
||||
readonly writable = new WritableStream<string>();
|
||||
readable!: ReadableStream<Uint8Array>;
|
||||
writable!: WritableStream<Uint8Array>;
|
||||
|
||||
constructor(url: string) {
|
||||
this.worker.addEventListener(
|
||||
"message",
|
||||
(event: MessageEvent<CCOSOutEvent>) => {
|
||||
if (event.data instanceof Uint8Array) {
|
||||
this.controller?.enqueue(event.data);
|
||||
return;
|
||||
}
|
||||
console.log("CCOS worker message", event.data);
|
||||
switch (event.data.type) {
|
||||
case "ready": {
|
||||
this.ready = true;
|
||||
this.resolveReady();
|
||||
break;
|
||||
}
|
||||
case "report": {
|
||||
this.onReport(event.data.modifiers, event.data.keys);
|
||||
break;
|
||||
}
|
||||
case "serial": {
|
||||
this.outStream.next(event.data.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -152,7 +152,29 @@ export class CCOS {
|
||||
} satisfies CCOSInitEvent);
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
getInfo(): SerialPortInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
async open(_options: SerialOptions) {
|
||||
this.readable = new ReadableStream<Uint8Array>({
|
||||
start: (controller) => {
|
||||
this.controller = controller;
|
||||
},
|
||||
});
|
||||
this.writable = new WritableStream<Uint8Array>({
|
||||
write: (chunk) => {
|
||||
this.worker.postMessage(chunk, [chunk.buffer]);
|
||||
},
|
||||
});
|
||||
return this.ready;
|
||||
}
|
||||
async close() {
|
||||
await this.ready;
|
||||
}
|
||||
async forget() {
|
||||
await this.ready;
|
||||
this.close();
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
@@ -198,7 +220,7 @@ export class CCOS {
|
||||
}
|
||||
|
||||
export async function fetchCCOS(
|
||||
version = ".test",
|
||||
version = ".2.2.0-beta.12+266bdda",
|
||||
fetch: typeof window.fetch = window.fetch,
|
||||
): Promise<CCOS | undefined> {
|
||||
const meta = await getMeta(device, version, fetch);
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
import("$lib/assets/layouts/generic/103-key.yml").then(
|
||||
(it) => it.default as VisualLayout,
|
||||
),
|
||||
ZERO: () =>
|
||||
import("$lib/assets/layouts/generic/103-key.yml").then(
|
||||
(it) => it.default as VisualLayout,
|
||||
),
|
||||
M4G: () =>
|
||||
import("$lib/assets/layouts/m4g.yml").then(
|
||||
(it) => it.default as VisualLayout,
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function getMeta(
|
||||
try {
|
||||
if (!browser) return fetchMeta(device, version, fetch);
|
||||
|
||||
const dbRequest = indexedDB.open("version-meta", 4);
|
||||
const dbRequest = indexedDB.open("version-meta", 5);
|
||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
dbRequest.onsuccess = () => resolve(dbRequest.result);
|
||||
dbRequest.onerror = () => reject(dbRequest.error);
|
||||
@@ -144,6 +144,10 @@ async function fetchMeta(
|
||||
)?.name ??
|
||||
undefined,
|
||||
esptool: meta?.update?.esptool ?? undefined,
|
||||
js: meta?.update?.js ?? undefined,
|
||||
wasm: meta?.update?.wasm ?? undefined,
|
||||
dll: meta?.update?.dll ?? undefined,
|
||||
so: meta?.update?.so ?? undefined,
|
||||
},
|
||||
spiFlash: meta?.spi_flash ?? undefined,
|
||||
};
|
||||
|
||||
@@ -52,6 +52,10 @@ export interface RawVersionMeta {
|
||||
ota: string | null;
|
||||
uf2: string | null;
|
||||
esptool: EspToolData | null;
|
||||
js: string | null;
|
||||
wasm: string | null;
|
||||
dll: string | null;
|
||||
so: string | null;
|
||||
};
|
||||
files: string[];
|
||||
spi_flash: SPIFlashInfo | null;
|
||||
@@ -78,6 +82,10 @@ export interface VersionMeta {
|
||||
ota?: string;
|
||||
uf2?: string;
|
||||
esptool?: EspToolData;
|
||||
js?: string;
|
||||
wasm?: string;
|
||||
dll?: string;
|
||||
so?: string;
|
||||
};
|
||||
spiFlash?: SPIFlashInfo;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
import { CharaDevice } from "$lib/serial/device";
|
||||
import { CharaDevice, type SerialPortLike } from "$lib/serial/device";
|
||||
import type { Chord } from "$lib/serial/chord";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { CharaLayout } from "$lib/serialization/layout";
|
||||
@@ -10,6 +10,10 @@ import type { VersionMeta } from "$lib/meta/types/meta";
|
||||
|
||||
export const serialPort = writable<CharaDevice | undefined>();
|
||||
|
||||
navigator.serial?.addEventListener("disconnect", async (event) => {
|
||||
serialPort.set(undefined);
|
||||
});
|
||||
|
||||
export interface SerialLogEntry {
|
||||
type: "input" | "output" | "system";
|
||||
value: string;
|
||||
@@ -59,9 +63,13 @@ export interface ProgressInfo {
|
||||
}
|
||||
export const syncProgress = writable<ProgressInfo | undefined>(undefined);
|
||||
|
||||
export async function initSerial(manual = false, withSync = true) {
|
||||
const device = get(serialPort) ?? new CharaDevice();
|
||||
await device.init(manual);
|
||||
export async function initSerial(port: SerialPortLike, withSync: boolean) {
|
||||
const prev = get(serialPort);
|
||||
try {
|
||||
prev?.close();
|
||||
} catch {}
|
||||
const device = new CharaDevice(port);
|
||||
await device.init();
|
||||
serialPort.set(device);
|
||||
if (withSync) {
|
||||
await sync();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { browser } from "$app/environment";
|
||||
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
|
||||
import semverGte from "semver/functions/gte";
|
||||
|
||||
const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
|
||||
export const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
|
||||
["ONE M0", { usbProductId: 32783, usbVendorId: 9114 }],
|
||||
["TWO S3 (pre-production)", { usbProductId: 0x8252, usbVendorId: 0x303a }],
|
||||
["TWO S3", { usbProductId: 0x8253, usbVendorId: 0x303a }],
|
||||
@@ -23,6 +23,42 @@ const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
|
||||
["T4G S2", { usbProductId: 0x82f2, usbVendorId: 0x303a }],
|
||||
]);
|
||||
|
||||
const DEVICE_ALIASES = new Map<string, Set<string>>([
|
||||
["CC1", new Set(["ONE M0", "one_m0"])],
|
||||
["CC2", new Set(["TWO S3", "two_s3", "TWO S3 (pre-production)"])],
|
||||
["Lite (S2)", new Set(["LITE S2", "lite_s2"])],
|
||||
["Lite (M0)", new Set(["LITE M0", "lite_m0"])],
|
||||
["CCX", new Set(["X", "ccx"])],
|
||||
["M4G", new Set(["M4G S3", "m4g_s3", "M4G S3 (pre-production)"])],
|
||||
["M4G (right)", new Set(["M4GR S3", "m4gr_s3"])],
|
||||
["T4G", new Set(["T4G S2", "t4g_s2"])],
|
||||
]);
|
||||
|
||||
export function getName(alias: string): string {
|
||||
for (const [name, aliases] of DEVICE_ALIASES.entries()) {
|
||||
if (aliases.has(alias)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return alias;
|
||||
}
|
||||
|
||||
export function getPortName(port: SerialPort): string {
|
||||
const { usbProductId, usbVendorId } = port.getInfo();
|
||||
console.log(port.getInfo());
|
||||
for (const [name, filter] of PORT_FILTERS.entries()) {
|
||||
if (
|
||||
filter.usbProductId === usbProductId &&
|
||||
filter.usbVendorId === usbVendorId
|
||||
) {
|
||||
return getName(name);
|
||||
}
|
||||
}
|
||||
return `Unknown Device (0x${usbVendorId?.toString(
|
||||
16,
|
||||
)}/0x${usbProductId?.toString(16)})`;
|
||||
}
|
||||
|
||||
const KEY_COUNTS = {
|
||||
ONE: 90,
|
||||
TWO: 90,
|
||||
@@ -31,6 +67,7 @@ const KEY_COUNTS = {
|
||||
M4G: 90,
|
||||
M4GR: 90,
|
||||
T4G: 7,
|
||||
ZERO: 256,
|
||||
} as const;
|
||||
|
||||
if (
|
||||
@@ -88,8 +125,12 @@ async function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||
]).finally(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
export type SerialPortLike = Pick<
|
||||
SerialPort,
|
||||
"readable" | "writable" | "open" | "close" | "getInfo" | "forget"
|
||||
>;
|
||||
|
||||
export class CharaDevice {
|
||||
private port!: SerialPort;
|
||||
private reader!: ReadableStreamDefaultReader<string>;
|
||||
|
||||
private readonly abortController1 = new AbortController();
|
||||
@@ -114,18 +155,13 @@ export class CharaDevice {
|
||||
return this.port.getInfo();
|
||||
}
|
||||
|
||||
constructor(private readonly baudRate = 115200) {}
|
||||
constructor(
|
||||
private readonly port: SerialPortLike,
|
||||
private readonly baudRate = 115200,
|
||||
) {}
|
||||
|
||||
async init(manual = false) {
|
||||
async init() {
|
||||
try {
|
||||
const ports = await getViablePorts();
|
||||
this.port =
|
||||
!manual && ports.length === 1
|
||||
? ports[0]!
|
||||
: await navigator.serial.requestPort({
|
||||
filters: [...PORT_FILTERS.values()],
|
||||
});
|
||||
|
||||
await this.port.open({ baudRate: this.baudRate });
|
||||
const info = this.port.getInfo();
|
||||
serialLog.update((it) => {
|
||||
@@ -242,6 +278,10 @@ export class CharaDevice {
|
||||
await this.port.forget();
|
||||
}
|
||||
|
||||
async close() {
|
||||
await this.port.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read/write to serial port
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
argbFromHex,
|
||||
themeFromSourceColor,
|
||||
} from "@material/material-color-utilities";
|
||||
import { canAutoConnect } from "$lib/serial/device";
|
||||
import { canAutoConnect, getViablePorts } from "$lib/serial/device";
|
||||
import { initSerial } from "$lib/serial/connection";
|
||||
import type { LayoutData } from "./$types";
|
||||
import { browser } from "$app/environment";
|
||||
@@ -63,7 +63,8 @@
|
||||
}
|
||||
|
||||
if (browser && $userPreferences.autoConnect && (await canAutoConnect())) {
|
||||
await initSerial();
|
||||
const [port] = await getViablePorts();
|
||||
await initSerial(port!, true);
|
||||
}
|
||||
|
||||
if (data.importFile) {
|
||||
|
||||
144
src/routes/(app)/ConnectPopup.svelte
Normal file
144
src/routes/(app)/ConnectPopup.svelte
Normal file
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { preference } from "$lib/preferences";
|
||||
import { initSerial } from "$lib/serial/connection";
|
||||
import {
|
||||
getPortName,
|
||||
PORT_FILTERS,
|
||||
type SerialPortLike,
|
||||
} from "$lib/serial/device";
|
||||
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let ports = $state<SerialPort[]>([]);
|
||||
|
||||
onMount(() => {
|
||||
refreshPorts();
|
||||
});
|
||||
|
||||
async function refreshPorts() {
|
||||
ports = await navigator.serial.getPorts();
|
||||
}
|
||||
|
||||
async function connect(port: SerialPortLike, withSync: boolean) {
|
||||
try {
|
||||
await initSerial(port, withSync);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await showConnectionFailedDialog(String(error));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="device-list">
|
||||
<fieldset>
|
||||
<label
|
||||
><input type="checkbox" use:preference={"autoConnect"} />
|
||||
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
><input type="checkbox" use:preference={"backup"} />
|
||||
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<button
|
||||
onclick={async (event) => {
|
||||
const { fetchCCOS } = await import("$lib/ccos/ccos");
|
||||
const ccos = await fetchCCOS();
|
||||
if (ccos) {
|
||||
connect(ccos, !event.shiftKey);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
CC0</button
|
||||
>
|
||||
{#each ports as port}
|
||||
<div class="device">
|
||||
<button
|
||||
onclick={(event) => {
|
||||
connect(port, !event.shiftKey);
|
||||
}}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
{getPortName(port)}</button
|
||||
>
|
||||
<button
|
||||
class="error"
|
||||
onclick={() => {
|
||||
port.forget();
|
||||
refreshPorts();
|
||||
}}><span class="icon">link_off</span></button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="pair">
|
||||
<button
|
||||
onclick={async (event) => {
|
||||
const port = await navigator.serial.requestPort({
|
||||
filters: event.shiftKey ? [] : [...PORT_FILTERS.values()],
|
||||
});
|
||||
if (!port) return;
|
||||
refreshPorts();
|
||||
connect(port, true);
|
||||
}}
|
||||
class="primary"><span class="icon">add</span>Pair</button
|
||||
>
|
||||
<a href="/ccos/zero_wasm/"><span class="icon">add</span>Virtual Device</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
button,
|
||||
a {
|
||||
padding: 10px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.pair {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.device {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
button.error {
|
||||
color: var(--md-sys-color-error);
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -7,16 +7,14 @@
|
||||
import { detectLocale, locales } from "$i18n/i18n-util";
|
||||
import { loadLocaleAsync } from "$i18n/i18n-util.async";
|
||||
import { tick } from "svelte";
|
||||
import SyncOverlay from "./SyncOverlay.svelte";
|
||||
import {
|
||||
initSerial,
|
||||
serialPort,
|
||||
sync,
|
||||
syncProgress,
|
||||
syncStatus,
|
||||
} from "$lib/serial/connection";
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
|
||||
import ConnectPopup from "./ConnectPopup.svelte";
|
||||
|
||||
let locale = $state(
|
||||
(browser && (localStorage.getItem("locale") as Locales)) || detectLocale(),
|
||||
@@ -48,20 +46,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function connect() {
|
||||
try {
|
||||
await initSerial(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await showConnectionFailedDialog(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect(event: MouseEvent) {
|
||||
if (event.shiftKey) {
|
||||
sync();
|
||||
} else {
|
||||
$serialPort?.forget();
|
||||
$serialPort?.close();
|
||||
$serialPort = undefined;
|
||||
}
|
||||
}
|
||||
@@ -90,9 +79,15 @@
|
||||
</ul>
|
||||
<div class="sync-box">
|
||||
{#if !$serialPort}
|
||||
<button class="warning" onclick={connect} transition:slide={{ axis: "x" }}
|
||||
<button
|
||||
class="warning"
|
||||
popovertarget="connect-popup"
|
||||
transition:slide={{ axis: "x" }}
|
||||
><span class="icon">usb</span>{$LL.deviceManager.CONNECT()}</button
|
||||
>
|
||||
<div popover id="connect-popup">
|
||||
<ConnectPopup />
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
transition:slide={{ axis: "x" }}
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
|
||||
async function connect() {
|
||||
try {
|
||||
await initSerial(true, false);
|
||||
const port = await navigator.serial.requestPort();
|
||||
await initSerial(port!, true);
|
||||
step = 1;
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
@@ -197,6 +198,10 @@
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#if data.meta.update.js && data.meta.update.wasm}
|
||||
<button>Add Virtual Device</button>
|
||||
{/if}
|
||||
|
||||
{#if data.meta.update.ota && !data.meta.device.endsWith("m0")}
|
||||
{@const buttonError = error || (!success && isCorrectDevice === false)}
|
||||
<section>
|
||||
@@ -260,6 +265,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.meta.update.uf2}
|
||||
<section>
|
||||
<ol>
|
||||
<li>
|
||||
@@ -301,6 +307,7 @@
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if false && data.meta.update.esptool}
|
||||
<section>
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
><td></td><td></td></tr
|
||||
>
|
||||
{/if}
|
||||
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord?.id))}
|
||||
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord]}
|
||||
{#if chord}
|
||||
<ChordEdit {chord} onduplicate={() => (page = 0)} />
|
||||
{/if}
|
||||
|
||||
@@ -33,6 +33,7 @@ const ccosFsPath = "/CCOS";
|
||||
|
||||
/** @type {any} */
|
||||
let ccos;
|
||||
let startTime = 0;
|
||||
|
||||
const semaphore = new AsyncSemaphore();
|
||||
|
||||
@@ -40,10 +41,17 @@ const semaphore = new AsyncSemaphore();
|
||||
* @param {MessageEvent<CCOSInEvent>} event
|
||||
*/
|
||||
self.addEventListener("message", async (event) => {
|
||||
if (event.data instanceof Uint8Array) {
|
||||
await semaphore.run(() => serialWrite(event.data));
|
||||
return;
|
||||
}
|
||||
switch (event.data.type) {
|
||||
case "init": {
|
||||
const url = event.data.url;
|
||||
await semaphore.run(() => init(url));
|
||||
/** @type {CCOSReadyEvent} */
|
||||
const readyMsg = { type: "ready" };
|
||||
self.postMessage(readyMsg);
|
||||
break;
|
||||
}
|
||||
case "press": {
|
||||
@@ -56,9 +64,6 @@ self.addEventListener("message", async (event) => {
|
||||
await semaphore.run(() => keyRelease(code));
|
||||
break;
|
||||
}
|
||||
case "serial": {
|
||||
await semaphore.run(() => serialWrite(event.data.data));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,37 +113,44 @@ async function init(url) {
|
||||
* @param {number} data
|
||||
*/
|
||||
(data) => {
|
||||
/** @type {CCOSInEvent}) */
|
||||
const msg = { type: "serial", data };
|
||||
self.postMessage(msg);
|
||||
const array = new Uint8Array([data]);
|
||||
self.postMessage(array, { transfer: [array.buffer] });
|
||||
},
|
||||
"vi",
|
||||
);
|
||||
|
||||
ccos._init(onReport, onSerial);
|
||||
startTime = performance.now();
|
||||
await ccos.ccall(
|
||||
"init",
|
||||
"void",
|
||||
["string", "number", "number"],
|
||||
[ccosFsPath, onReport, onSerial],
|
||||
{ async: true },
|
||||
);
|
||||
|
||||
async function update() {
|
||||
if (ccos) {
|
||||
await semaphore.run(() => ccos.update());
|
||||
await semaphore.run(() => {
|
||||
ccos.update(performance.now());
|
||||
});
|
||||
}
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
update();
|
||||
|
||||
/** @type {CCOSReadyEvent} */
|
||||
const readyMsg = { type: "ready" };
|
||||
self.postMessage(readyMsg);
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} data
|
||||
* @param {Uint8Array} data
|
||||
*/
|
||||
async function serialWrite(data) {
|
||||
if (!ccos) {
|
||||
console.warn("Serial write ignored, CCOS is not initialized.");
|
||||
return;
|
||||
}
|
||||
await ccos.serialWrite(data);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
await ccos.serialWrite(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user