From 15f5056a5956ae7aeea2c21fad6dd4b286f5c65a Mon Sep 17 00:00:00 2001 From: wvffle Date: Thu, 20 Oct 2022 14:46:58 +0000 Subject: [PATCH] Add web worker communication for offloading the main thread --- front/src/composables/audio/usePlayOptions.ts | 2 +- front/src/composables/useWebWorker.ts | 5 ++ front/src/init/worker.ts | 6 +++ front/src/worker/communication.ts | 53 +++++++++++++++++++ front/src/worker/modules/queue.ts | 30 +++++++++++ front/src/worker/webworker.ts | 4 ++ front/tsconfig.json | 1 + front/vite.config.ts | 1 + 8 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 front/src/composables/useWebWorker.ts create mode 100644 front/src/init/worker.ts create mode 100644 front/src/worker/communication.ts create mode 100644 front/src/worker/modules/queue.ts create mode 100644 front/src/worker/webworker.ts diff --git a/front/src/composables/audio/usePlayOptions.ts b/front/src/composables/audio/usePlayOptions.ts index 136b9608b..3e0cadf46 100644 --- a/front/src/composables/audio/usePlayOptions.ts +++ b/front/src/composables/audio/usePlayOptions.ts @@ -45,7 +45,7 @@ export default (props: PlayOptionsProps) => { }) const filterableArtist = computed(() => props.track?.artist ?? props.album?.artist ?? props.artist) - const filterArtist = () => store.dispatch('moderation/hide', { type: 'artist', target: filterableArtist.value }) + const filterArtist = async () => store.dispatch('moderation/hide', { type: 'artist', target: filterableArtist.value }) const { $npgettext } = useGettext() const addMessage = (tracks: Track[]) => { diff --git a/front/src/composables/useWebWorker.ts b/front/src/composables/useWebWorker.ts new file mode 100644 index 000000000..161478ef7 --- /dev/null +++ b/front/src/composables/useWebWorker.ts @@ -0,0 +1,5 @@ +import { createMessageChannel } from '#/communication' + +export default (channel: string) => { + return createMessageChannel(channel) +} diff --git a/front/src/init/worker.ts b/front/src/init/worker.ts new file mode 100644 index 000000000..363e8c43d --- /dev/null +++ b/front/src/init/worker.ts @@ -0,0 +1,6 @@ +import { registerMainThread } from '#/communication' + +import Worker from '~/worker/webworker?worker' + +export const worker = new Worker() +registerMainThread(worker) diff --git a/front/src/worker/communication.ts b/front/src/worker/communication.ts new file mode 100644 index 000000000..67e98caea --- /dev/null +++ b/front/src/worker/communication.ts @@ -0,0 +1,53 @@ +import type { EventHook, EventHookOn } from '@vueuse/core' + +import { createEventHook } from '@vueuse/core' + +interface MessageChannel { + hook: EventHook + post: (data: unknown) => void + onMessageReceived: EventHookOn +} + +let postMessageFn: typeof postMessage | typeof Worker.prototype.postMessage = () => { + throw new Error('Thread is unregistered') +} + +const onMessageFn = (event: MessageEvent) => { + const { channel, data } = JSON.parse(event.data) + messageChannels.get(channel)?.hook.trigger(data) +} + +export const registerMainThread = (worker: Worker) => { + worker.onmessage = onMessageFn + postMessageFn = (message: string) => { + worker.postMessage(message) + } +} + +export const registerWorkerThread = () => { + onmessage = onMessageFn + postMessageFn = (message: string) => { + postMessage(message) + } +} + +const messageChannels = new Map() +export const createMessageChannel = (channel: string): MessageChannel => { + if (messageChannels.has(channel)) { + const messageChannel = messageChannels.get(channel) + if (messageChannel) return messageChannel + } + + const hook = createEventHook() + const messageChannel: MessageChannel = { + hook, + onMessageReceived: hook.on, + post: (data: unknown) => postMessageFn(JSON.stringify({ + channel, + data + })) + } + + messageChannels.set(channel, messageChannel) + return messageChannel +} diff --git a/front/src/worker/modules/queue.ts b/front/src/worker/modules/queue.ts new file mode 100644 index 000000000..93a6636ea --- /dev/null +++ b/front/src/worker/modules/queue.ts @@ -0,0 +1,30 @@ +import type { Track } from '~/types' + +import { createMessageChannel } from '#/communication' + +const { onMessageReceived, post } = createMessageChannel('queue') + +const queue: Track[] = [] + +onMessageReceived((data) => { + switch (data.type) { + case 'queue': + queue.length = 0 + queue.push(...data.tracks) + break + + case 'enqueue': + queue.push(...data.tracks) + post({ type: 'queue', tracks: queue }) + break + + case 'shuffle': + // TODO: Shuffle queue + post({ type: 'queue', tracks: [] }) + break + + case 'unshuffle': + post({ type: 'queue', tracks: queue }) + break + } +}) diff --git a/front/src/worker/webworker.ts b/front/src/worker/webworker.ts new file mode 100644 index 000000000..14fc5e5f1 --- /dev/null +++ b/front/src/worker/webworker.ts @@ -0,0 +1,4 @@ +import { registerWorkerThread } from '#/communication' +registerWorkerThread() + +import.meta.glob('./modules/*.ts', { eager: true }) diff --git a/front/tsconfig.json b/front/tsconfig.json index 258209fb5..6a8a206ff 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -14,6 +14,7 @@ "vite-plugin-pwa/client" ], "paths": { + "#/*": ["src/worker/*"], "?/*": ["test/*"], "~/*": ["src/*"] } diff --git a/front/vite.config.ts b/front/vite.config.ts index 55724e6cf..b7e091906 100644 --- a/front/vite.config.ts +++ b/front/vite.config.ts @@ -86,6 +86,7 @@ export default defineConfig(({ mode }) => ({ }, resolve: { alias: { + '#': resolve(__dirname, './src/worker'), '?': resolve(__dirname, './test'), '~': resolve(__dirname, './src') }