@fcanvas/worker
Version:
The plugin provides support for using fCanvas in WebWorker
366 lines (359 loc) • 10.9 kB
JavaScript
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 };