UNPKG

@fcanvas/worker

Version:

The plugin provides support for using fCanvas in WebWorker

366 lines (359 loc) 10.9 kB
import { put, listen, ping, uuid } from '@fcanvas/communicate'; import { Layer, CANVAS_ELEMENT, REMOVE_EVENT, ADD_EVENT, watchEffect, toRaw, STORE_EVENTS, RAW_MAP_LISTENERS, NOOP } from 'fcanvas'; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/logic/getPropsNameEvent.ts function getPropsNameEvent(event) { var _a; const props = Object.keys(event); let prototype = event; while (((_a = prototype.constructor) == null ? void 0 : _a.name) !== "Event" && (prototype = Object.getPrototypeOf(prototype))) props.push(...Object.keys(prototype)); return props; } // src/logic/porters/TouchList.ts function copyObjectWithoutNode(obj) { const clone = {}; getPropsNameEvent(obj).forEach((prop) => { const value = obj[prop]; clone[prop] = value instanceof Node ? null : value; }); return clone; } function createPortTouchList(value) { return { __v_type: "TouchList", value: Array.from(value).map(copyObjectWithoutNode) }; } function resolvePortTouchList({ value }) { return Object.assign({}, value, { item(index) { var _a; return (_a = value[index]) != null ? _a : null; }, get length() { return value.length; }, *[Symbol.iterator]() { for (let i = 0; i < value.length; i += 1) yield value[i]; }, [Symbol.toStringTag]: "TouchList" }); } function createPortFn(port2, fn) { const id = uuid(); const stop = listen(port2, id, fn); setTimeout(stop, 1e3); return { __v_port: true, id }; } function resolvePortFn(port1, ported) { return (...args) => { return put(port1, ported.id, ...args); }; } // src/logic/porters/Event.ts var rInitXEvent = /^init\w*Event$/; function isPropDeprecated(prop) { if (prop === "cancelBubble" || prop === "returnValue" || prop === "srcElement") return true; if (rInitXEvent.test(prop)) return true; return false; } var propsBypass = [ "preventDefault", "stopPropagation", "stopImmediatePropagation" ]; function isPropBypass(prop) { return propsBypass.includes(prop); } function type(value) { return (value + "").slice(8, -1); } function createFakeEvent(port2, event, bypassFn) { const props = getPropsNameEvent(event); const fake = {}; props.forEach((prop) => { if (isPropDeprecated(prop)) return; if (isPropBypass(prop)) { fake[prop] = { __v_noop: true }; return; } const value = event[prop]; if (value instanceof Node) { fake[prop] = null; return; } if (typeof value === "function") { fake[prop] = bypassFn ? NOOP : createPortFn(port2, value.bind(event)); return; } switch (type(value)) { case "TouchList": fake[prop] = createPortTouchList(value); break; case "Window": fake[prop] = null; break; default: if (value && typeof value === "object") fake[prop] = copyObjectWithoutNode(value); else fake[prop] = value; } }); return fake; } function resolveFakeEvent(port1, fake) { Object.entries(fake).forEach(([prop, value]) => { if (value == null ? void 0 : value.__v_port) fake[prop] = resolvePortFn(port1, value); if ((value == null ? void 0 : value.__v_type) === "TouchList") fake[prop] = resolvePortTouchList(value); if (value == null ? void 0 : value.__v_noop) fake[prop] = NOOP; }); return fake; } // src/port-from-thread.ts var store = /* @__PURE__ */ new WeakMap(); function portToWorker(worker, stage) { return __async(this, null, function* () { if (store.has(stage)) { if (process.env.NODE_ENV !== "production") console.warn("[fcanvas/worker]: This 'Stage' is already connected."); return; } const port2 = yield put(worker, "fw_create_connect" /* CREATE_CONNECT */); port2.start(); const storeLayers = /* @__PURE__ */ new Map(); const listenEvents = /* @__PURE__ */ new Map(); listen( port2, "update_layer", (value) => { storeLayers.forEach((layer, uid) => { if (!(uid in value)) { storeLayers.delete(uid); stage.delete(layer); } }); const offscreens = {}; const offs = []; Object.entries(value).forEach(([uid, item]) => { const layerExists = storeLayers.get(uid); if (layerExists) { Object.assign(layerExists.$, __spreadProps(__spreadValues({}, item.$), { width: item.width, height: item.height })); return; } const layer = new Layer(__spreadProps(__spreadValues({}, item.$), { autoDraw: false, width: item.width, height: item.height })); layer.uid = uid; storeLayers.set(uid, layer); stage.add(layer); offs.push( offscreens[uid] = layer[CANVAS_ELEMENT].transferControlToOffscreen() ); }); if (process.env.NODE_ENV !== "production") console.log("layers: ", storeLayers); return { return: offscreens, transfer: offs }; } ); listen( port2, "listen_events", (value) => { listenEvents.forEach((cb, name) => { if (value.some((item) => item.name === name)) { listenEvents.delete(name); stage[REMOVE_EVENT](name, cb); } }); value.forEach(({ name, offs, prevent }) => { if (listenEvents.has(name)) return; listenEvents.set(name, handle); stage[ADD_EVENT](name, handle); function handle(event) { if (prevent) event.preventDefault(); ping(port2, "emit_event", { name: event.type, event: createFakeEvent(port2, event), info: offs.reduce( (r, uid) => { const layer = storeLayers.get(uid); if (!layer) { if (process.env.NODE_ENV !== "production") { console.warn( "[@fcanvas/worker]: Layer '%s' not found.", uid ); } return r; } const el = layer[CANVAS_ELEMENT]; const { left, top } = el.getBoundingClientRect(); const { width, height, scrollWidth, scrollHeight } = el; r[uid] = { left, top, width, height, scrollWidth, scrollHeight }; return r; }, {} ) }); } }); } ); yield put(worker, "fw_setup_done" /* SETUP_DONE */); }); } var store2 = /* @__PURE__ */ new WeakMap(); function portToThread(stage) { return __async(this, null, function* () { if (store2.has(stage)) { if (process.env.NODE_ENV !== "production") console.warn("[fcanvas/worker]: This 'Stage' is already connected."); return; } let channel; listen(self, "fw_create_connect" /* CREATE_CONNECT */, () => { channel = new MessageChannel(); store2.set(stage, channel); return { return: channel.port2, transfer: [channel.port2] }; }); listen(self, "fw_setup_done" /* SETUP_DONE */, () => { const storeLayers = /* @__PURE__ */ new Map(); channel.port1.start(); watchEffect(() => { const value = Array.from(stage.children.values()).reduce( (r, layer) => { storeLayers.set(layer.uid, layer); r[layer.uid] = { $: toRaw(layer.$), width: layer[CANVAS_ELEMENT].width, height: layer[CANVAS_ELEMENT].height }; return r; }, {} ); storeLayers.forEach((layer, uid) => { if (!(uid in value)) storeLayers.delete(uid); }); put(channel.port1, "update_layer", value).then( (value2) => { Object.entries(value2).forEach(([uid, off]) => { const layer = storeLayers.get(uid); if (!layer) return; layer[CANVAS_ELEMENT] = off; layer.markChange(); }); return; } ); }); listen( channel.port1, "emit_event", (value) => { var _a; const { name, event, info } = value; const ev = resolveFakeEvent(channel.port1, event); Object.assign(ev, { info }); (_a = stage[STORE_EVENTS].get(name)) == null ? void 0 : _a.handle(ev); } ); watchEffect(() => { const value = []; stage[STORE_EVENTS].forEach((_, eventName) => { var _a; const offs = []; let prevent = false; for (const dep of _.deps.values()) { const els = (_a = stage[RAW_MAP_LISTENERS].get(dep)) == null ? void 0 : _a.keys(); if (!els) continue; for (const el of els) { if (el === stage) continue; if (!prevent) prevent = true; offs.push(el.uid); } } value.push({ name: eventName, offs, prevent }); }); ping(channel.port1, "listen_events", value); }); }); }); } export { portToThread, portToWorker };