Skip to content

Commit

Permalink
refactor: use generator function for generic worker
Browse files Browse the repository at this point in the history
This spawns the worker only when needed, avoiding the extra memory usage of keeping it running all the time

Also properly release the worker in JVirtual

Signed-off-by: Fernando Fernández <[email protected]>
  • Loading branch information
ferferga committed Dec 6, 2024
1 parent d956503 commit 46a43e5
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 23 deletions.
7 changes: 5 additions & 2 deletions frontend/src/components/lib/JVirtual/JVirtual.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
type StyleValue,
useTemplateRef
} from 'vue';
import { wrap } from 'comlink';
import { releaseProxy, wrap } from 'comlink';
import {
getBufferMeta,
getContentSize,
Expand Down Expand Up @@ -356,7 +356,10 @@ watch([bufferLength, resizeMeasurement, itemsLength, bufferOffset], (val, oldVal
});
onBeforeUnmount(destroyEventListeners);
onBeforeUnmount(() => workerInstance.terminate());
onBeforeUnmount(() => {
worker[releaseProxy]();
workerInstance.terminate();
});
onBeforeUnmount(() => cache.clear());
</script>

Expand Down
1 change: 1 addition & 0 deletions frontend/src/plugins/workers/generic.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class GenericWorker {
*/
public shuffle<T>(array: T[]) {
for (let i = array.length - 1; i > 0; i--) {
// eslint-disable-next-line sonarjs/pseudo-random
const j = Math.floor(Math.random() * (i + 1));

[array[i], array[j]] = [array[j], array[i]];
Expand Down
36 changes: 32 additions & 4 deletions frontend/src/plugins/workers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { wrap } from 'comlink';
import { toRaw } from 'vue';
import { releaseProxy, wrap } from 'comlink';
import type { IBlurhashDecoder } from './blurhash-decoder.worker';
import BlurhashDecoder from './blurhash-decoder.worker?worker';
import type { ICanvasDrawer } from './canvas-drawer.worker';
Expand Down Expand Up @@ -27,7 +28,34 @@ export const blurhashDecoder = wrap<IBlurhashDecoder>(new BlurhashDecoder());
export const canvasDrawer = wrap<ICanvasDrawer>(new CanvasDrawer());

/**
* A worker for running any non-specific function that could be expensive and take some time to complete,
* blocking the main thread
* The generic worker is a class that must implement all functions that could take some time to complete
* and could potentially block the main thread.
*
* With his function, we instantiate the worker, run the function and then cleanup it immediately.
*
* The function is also prepared for receiving Vue reactive objects,
* converting them beforehand.
*/
export const genericWorker = wrap<IGenericWorker>(new GenericWorker());
export function runGenericWorkerFunc<
T extends keyof IGenericWorker,
P extends Parameters<IGenericWorker[T]> = Parameters<IGenericWorker[T]>,
R extends ReturnType<IGenericWorker[T]> = ReturnType<IGenericWorker[T]>
>(key: T) {
return async (...args: P): Promise<R | undefined> => {
const workerInstance = new GenericWorker();
const genericWorker = wrap<IGenericWorker>(workerInstance);

try {
/**
* The `toRaw` function is used to convert a reactive object into a plain object
* before passing to the worker
*/
return await genericWorker[key](...args.map(arg => toRaw(arg)));
} catch {
return undefined;
} finally {
genericWorker[releaseProxy]();
workerInstance.terminate();
}
};
}
6 changes: 3 additions & 3 deletions frontend/src/store/playback-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import playbackProfile from '@/utils/playback-profiles';
import { msToTicks } from '@/utils/time';
import { mediaControls, mediaElementRef } from '@/store';
import { CommonStore } from '@/store/super/common-store';
import { shuffle } from '@/utils/data-manipulation';
import { runGenericWorkerFunc } from '@/plugins/workers';

/**
* == INTERFACES AND TYPES ==
Expand Down Expand Up @@ -614,7 +614,7 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
this._state.currentItemIndex = startFromIndex;

if (startShuffled) {
void this.toggleShuffle(false);
await this.toggleShuffle(false);
}

this.currentTime = startFromTime;
Expand Down Expand Up @@ -779,7 +779,7 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
this._state.originalQueue = [];
this._state.isShuffling = false;
} else {
const queue = await shuffle(this._state.queue);
const queue = await runGenericWorkerFunc('shuffle')(this._state.queue) ?? this._state.originalQueue;

this._state.originalQueue = this._state.queue;

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/store/player-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { CommonStore } from '@/store/super/common-store';
import { router } from '@/plugins/router';
import { remote } from '@/plugins/remote';
import type { ParsedSubtitleTrack } from '@/plugins/workers/generic/subtitles';
import { genericWorker } from '@/plugins/workers';
import { runGenericWorkerFunc } from '@/plugins/workers';
import { subtitleSettings } from '@/store/client-settings/subtitle-settings';

/**
Expand Down Expand Up @@ -202,7 +202,7 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
* otherwise show default subtitle track
*/
if (this._useCustomSubtitleTrack) {
const data = await genericWorker.parseVttFile(subtitleTrack.src);
const data = await runGenericWorkerFunc('parseVttFile')(subtitleTrack.src);

this.currentExternalSubtitleTrack.parsed = data;
} else {
Expand Down
12 changes: 0 additions & 12 deletions frontend/src/utils/data-manipulation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { defu } from 'defu';
import { toRaw } from 'vue';
import { genericWorker } from '@/plugins/workers';

/**
* Merge 2 objects, excluding the keys from the destination that are not present in source
Expand Down Expand Up @@ -70,13 +68,3 @@ export function pick<T extends object>(object: T, keys: (keyof T)[]): Partial<T>

return res;
}

/**
* Shuffles an array in a WebWorker using the Durstenfeld shuffle algorithm, an
* optimized version of Fisher-Yates shuffle.
*
* It's also prepared for the case when the array is reactive thorugh Vue's `ref` or `reactive`.
*/
export async function shuffle<T>(array: T[]): Promise<T[]> {
return await genericWorker.shuffle(toRaw(array)) as T[];
}

0 comments on commit 46a43e5

Please sign in to comment.