no worker

This commit is contained in:
tamaina 2023-05-17 18:15:48 +00:00
parent 732e7dc931
commit a9c49e4fb4
5 changed files with 40 additions and 164 deletions

View file

@ -16,31 +16,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import DrawBlurhash from '@/workers/draw-blurhash?worker';
import TestWebGL2 from '@/workers/test-webgl2?worker';
import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
import { $ref } from 'vue/macros'; import { $ref } from 'vue/macros';
const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => { const WEBGL2_IN_OFFSCREENCANVAS_SUPPORTED = (() => {
const testWorker = new TestWebGL2(); const canvas = new OffscreenCanvas(1, 1);
testWorker.addEventListener('message', event => { const gl = canvas.getContext('webgl2');
if (event.data.result) { if (_DEV_) console.info('WEBGL2_IN_OFFSCREENCANVAS_SUPPORTED', !!gl);
const workers = new WorkerMultiDispatch( return !!gl;
() => new DrawBlurhash(), })();
Math.min(navigator.hardwareConcurrency - 1, 4),
);
resolve(workers);
} else {
resolve(null);
}
testWorker.terminate();
});
});
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, shallowRef, useCssModule, watch } from 'vue'; import { computed, nextTick, onMounted, shallowRef, useCssModule, watch } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha'; import { render } from 'buraha';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
const $style = useCssModule(); const $style = useCssModule();
@ -74,7 +61,6 @@ const props = withDefaults(defineProps<{
forceBlurhash: false, forceBlurhash: false,
}); });
const viewId = uuid();
const canvas = shallowRef<HTMLCanvasElement>(); const canvas = shallowRef<HTMLCanvasElement>();
const root = shallowRef<HTMLDivElement>(); const root = shallowRef<HTMLDivElement>();
const img = shallowRef<HTMLImageElement>(); const img = shallowRef<HTMLImageElement>();
@ -83,7 +69,6 @@ let canvasWidth = $ref(64);
let canvasHeight = $ref(64); let canvasHeight = $ref(64);
let imgWidth = $ref(props.width); let imgWidth = $ref(props.width);
let imgHeight = $ref(props.height); let imgHeight = $ref(props.height);
let usingWorkerNumber = $ref<number>(-1);
const hide = computed(() => !loaded || props.forceBlurhash); const hide = computed(() => !loaded || props.forceBlurhash);
function waitForDecode() { function waitForDecode() {
@ -118,38 +103,41 @@ watch([() => props.width, () => props.height, root], () => {
immediate: true, immediate: true,
}); });
async function draw(transfer: boolean = false) { // webgl2burahacanvas
if (!canvas.value || props.hash == null) return; // ImageBitmapwebgl2canvas
const workers = await workerPromise; async function drawSub(): Promise<ImageBitmap | void> {
if (workers) { if (props.hash == null) return;
let offscreen: OffscreenCanvas | undefined;
if (transfer) { if (WEBGL2_IN_OFFSCREENCANVAS_SUPPORTED) {
offscreen = canvas.value.transferControlToOffscreen(); const work = new OffscreenCanvas(canvasWidth, canvasHeight);
render(props.hash, work);
return createImageBitmap(work);
} }
const workerNumber = workers.postMessage(
{
id: viewId,
canvas: offscreen ?? undefined,
hash: props.hash,
},
offscreen ? [offscreen] : [],
usingWorkerNumber === -1 ? undefined : () => usingWorkerNumber,
);
usingWorkerNumber = workerNumber;
} else {
try {
const work = document.createElement('canvas'); const work = document.createElement('canvas');
work.width = canvasWidth; work.width = canvasWidth;
work.height = canvasHeight; work.height = canvasHeight;
render(props.hash, work); render(props.hash, work);
const bitmap = await createImageBitmap(work); return createImageBitmap(work);
}
async function draw() {
if (!canvas.value || props.hash == null) return;
const ctx = canvas.value.getContext('2d'); const ctx = canvas.value.getContext('2d');
ctx?.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
if (!ctx) {
console.error('Unable to get canvas 2D context');
return;
}
try {
const bitmap = await drawSub();
if (!bitmap) return;
ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
} catch (error) { } catch (error) {
console.error('Error occured during drawing blurhash', error); console.error('Error occured during drawing blurhash', error);
} }
} }
}
watch(() => props.src, () => { watch(() => props.src, () => {
waitForDecode(); waitForDecode();
@ -160,17 +148,9 @@ watch(() => props.hash, () => {
}); });
onMounted(() => { onMounted(() => {
draw(true); draw();
waitForDecode(); waitForDecode();
}); });
onUnmounted(() => {
if (usingWorkerNumber !== -1) {
workerPromise.then(worker => {
worker?.postMessage!({ id: viewId, delete: true }, undefined, () => usingWorkerNumber);
});
}
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View file

@ -1,62 +0,0 @@
function defaultUseWorkerNumber(prev: number, totalWorkers: number) {
return prev + 1;
}
export class WorkerMultiDispatch<POST = any, RETURN = any> {
private sym = Symbol('WorkerMultiDispatch');
public workers: Worker[] = [];
private prevWorkerNumber = 0;
private getUseWorkerNumber = defaultUseWorkerNumber;
private finalizationRegistry: FinalizationRegistry<symbol>;
constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
this.getUseWorkerNumber = getUseWorkerNumber;
for (let i = 0; i < concurrency; i++) {
this.workers.push(workerConstructor());
}
this.finalizationRegistry = new FinalizationRegistry(() => {
this.terminate();
});
this.finalizationRegistry.register(this, this.sym);
if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
}
public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) {
let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length);
workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length;
if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber);
this.prevWorkerNumber = workerNumber;
// 不毛だがunionをoverloadに突っ込めない
// https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error
// https://github.com/microsoft/TypeScript/issues/14107
if (Array.isArray(options)) {
this.workers[workerNumber].postMessage(message, options);
} else {
this.workers[workerNumber].postMessage(message, options);
}
return workerNumber;
}
public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
this.workers.forEach(worker => {
worker.addEventListener('message', callback, options);
});
}
public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
this.workers.forEach(worker => {
worker.removeEventListener('message', callback, options);
});
}
public terminate() {
this.workers.forEach(worker => {
worker.terminate();
});
this.workers = [];
this.finalizationRegistry.unregister(this);
}
}

View file

@ -1,30 +0,0 @@
import { render } from 'buraha';
const canvases = new Map<string, OffscreenCanvas>();
onmessage = async (event) => {
// console.log(event.data);
if (!('id' in event.data && typeof event.data.id === 'string')) {
return;
}
if (event.data.delete) {
canvases.delete(event.data.id);
return;
}
if (event.data.canvas) {
canvases.set(event.data.id, event.data.canvas);
}
if (!('hash' in event.data && typeof event.data.hash === 'string')) {
return;
}
const canvas = event.data.canvas ?? canvases.get(event.data.id);
if (!canvas) {
throw new Error('No canvas');
}
const work = new OffscreenCanvas(canvas.width, canvas.height);
render(event.data.hash, work);
const bitmap = await createImageBitmap(work);
const ctx = canvas.getContext('2d');
ctx?.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
postMessage({ result: true });
};

View file

@ -1,7 +0,0 @@
const canvas = new OffscreenCanvas(1, 1);
const gl = canvas.getContext('webgl2');
if (gl) {
postMessage({ result: true });
} else {
postMessage({ result: false });
}

View file

@ -1,5 +0,0 @@
{
"compilerOptions": {
"lib": ["esnext", "webworker"],
}
}