diff --git a/package-lock.json b/package-lock.json index 5d79fae..5150206 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3630,8 +3630,8 @@ "dependencies": { "lil-gui": "~0.19.0", "three": "~0.158.0", - "wtd-core": "~3.0.0-next.0", - "wtd-three-ext": "~3.0.0-next.0", + "wtd-core": "~3.0.0-next.1", + "wtd-three-ext": "~3.0.0-next.1", "wwobjloader2": "6.2.0-next.4" }, "devDependencies": { @@ -3640,15 +3640,15 @@ } }, "packages/wtd-core": { - "version": "3.0.0-next.0", + "version": "3.0.0-next.1", "license": "MIT" }, "packages/wtd-three-ext": { - "version": "3.0.0-next.0", + "version": "3.0.0-next.1", "license": "MIT", "dependencies": { "three": "~0.158.0", - "wtd-core": "~3.0.0-next.0" + "wtd-core": "~3.0.0-next.1" }, "devDependencies": { "@types/three": "~0.158.1" diff --git a/packages/examples/package.json b/packages/examples/package.json index 82a923d..927a665 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -30,8 +30,8 @@ "dependencies": { "lil-gui": "~0.19.0", "three": "~0.158.0", - "wtd-core": "~3.0.0-next.0", - "wtd-three-ext": "~3.0.0-next.0", + "wtd-core": "~3.0.0-next.1", + "wtd-three-ext": "~3.0.0-next.1", "wwobjloader2": "6.2.0-next.4" }, "devDependencies": { diff --git a/packages/examples/src/com/WorkerCom.ts b/packages/examples/src/com/WorkerCom.ts index 06b1457..187b31b 100644 --- a/packages/examples/src/com/WorkerCom.ts +++ b/packages/examples/src/com/WorkerCom.ts @@ -5,6 +5,7 @@ import { WorkerTaskMessageConfig } from 'wtd-core'; import { recalcAspectRatio, updateText } from '../worker/ComWorkerCommon.js'; +import { OffscreenPayload } from 'wtd-three-ext'; /** * Hello World example using a classic worker @@ -53,27 +54,37 @@ class HelloWorldStandardWorkerExample { try { const offscreenCom1 = this.canvasCom1.transferControlToOffscreen(); + const offscreenPayload1 = new OffscreenPayload({ + event: { + type: 'init', + drawingSurface: offscreenCom1, + width: this.canvasCom1.clientWidth, + height: this.canvasCom1.clientHeight + } + }); const payload1 = new RawPayload({ - port: channel.port1, - drawingSurface: offscreenCom1, - width: this.canvasCom1.clientWidth, - height: this.canvasCom1.clientHeight + port: channel.port1 }); const offscreenCom2 = this.canvasCom2.transferControlToOffscreen(); + const offscreenPayload2 = new OffscreenPayload({ + event: { + type: 'init', + drawingSurface: offscreenCom2, + width: this.canvasCom2.clientWidth, + height: this.canvasCom2.clientHeight + } + }); const payload2 = new RawPayload({ - port: channel.port2, - drawingSurface: offscreenCom2, - width: this.canvasCom2.clientWidth, - height: this.canvasCom2.clientHeight + port: channel.port2 }); const promisesinit = []; promisesinit.push(this.workerTaskCom1.initWorker({ - message: WorkerTaskMessage.fromPayload(payload1), + message: WorkerTaskMessage.fromPayload([offscreenPayload1, payload1]), transferables: [channel.port1, offscreenCom1] })); promisesinit.push(this.workerTaskCom2.initWorker({ - message: WorkerTaskMessage.fromPayload(payload2), + message: WorkerTaskMessage.fromPayload([offscreenPayload2, payload2]), transferables: [channel.port2, offscreenCom2], })); await Promise.all(promisesinit); diff --git a/packages/examples/src/worker/Com1Worker.ts b/packages/examples/src/worker/Com1Worker.ts index 6c8fac6..5c3d04f 100644 --- a/packages/examples/src/worker/Com1Worker.ts +++ b/packages/examples/src/worker/Com1Worker.ts @@ -10,6 +10,7 @@ import { WorkerTaskWorker } from 'wtd-core'; import { getOffScreenCanvas, updateText } from './ComWorkerCommon.js'; +import { OffscreenPayload } from 'wtd-three-ext'; export class Com1Worker implements WorkerTaskWorker, InterComWorker { @@ -17,10 +18,8 @@ export class Com1Worker implements WorkerTaskWorker, InterComWorker { private offScreenCanvas?: HTMLCanvasElement; init(message: WorkerTaskMessageConfig): void { - // register the default com-routing function for inter-worker communication - const payload = message.payloads?.[0]; - this.icph.registerPort('com2', payload, message => comRouting(this, message)); - this.offScreenCanvas = getOffScreenCanvas(payload); + const payloadOffscreen = message.payloads?.[0] as OffscreenPayload; + this.offScreenCanvas = getOffScreenCanvas(payloadOffscreen); updateText({ text: 'Worker 1: init', width: this.offScreenCanvas?.width ?? 0, @@ -29,6 +28,10 @@ export class Com1Worker implements WorkerTaskWorker, InterComWorker { log: true }); + // register the default com-routing function for inter-worker communication + const payloadPort = message.payloads?.[1]; + this.icph.registerPort('com2', payloadPort, message => comRouting(this, message)); + // send initComplete to main const initComplete = WorkerTaskMessage.createFromExisting({} as WorkerTaskMessageConfig, WorkerTaskCommandResponse.INIT_COMPLETE); const payloadResponse = new RawPayload({ hello: 'Worker 1 initComplete!' }); diff --git a/packages/examples/src/worker/Com2Worker.ts b/packages/examples/src/worker/Com2Worker.ts index 6e6b15c..62afa3c 100644 --- a/packages/examples/src/worker/Com2Worker.ts +++ b/packages/examples/src/worker/Com2Worker.ts @@ -10,6 +10,7 @@ import { WorkerTaskWorker } from 'wtd-core'; import { getOffScreenCanvas, updateText } from './ComWorkerCommon.js'; +import { OffscreenPayload } from 'wtd-three-ext'; export class Com2Worker implements WorkerTaskWorker, InterComWorker { @@ -17,10 +18,8 @@ export class Com2Worker implements WorkerTaskWorker, InterComWorker { private offScreenCanvas?: HTMLCanvasElement; init(message: WorkerTaskMessageConfig): void { - // register the default com-routing function for inter-worker communication - const payload = message.payloads?.[0]; - this.icph.registerPort('com1', payload, message => comRouting(this, message)); - this.offScreenCanvas = getOffScreenCanvas(payload); + const payloadOffscreen = message.payloads?.[0] as OffscreenPayload; + this.offScreenCanvas = getOffScreenCanvas(payloadOffscreen); updateText({ text: 'Worker 2: init', width: this.offScreenCanvas?.width ?? 0, @@ -29,6 +28,10 @@ export class Com2Worker implements WorkerTaskWorker, InterComWorker { log: true }); + // register the default com-routing function for inter-worker communication + const payloadPort = message.payloads?.[1]; + this.icph.registerPort('com1', payloadPort, message => comRouting(this, message)); + // send initComplete to main const initComplete = WorkerTaskMessage.createFromExisting({} as WorkerTaskMessageConfig, WorkerTaskCommandResponse.INIT_COMPLETE); const payloadResponse = new RawPayload({ hello: 'Worker 2 initComplete!' }); diff --git a/packages/examples/src/worker/ComWorkerCommon.ts b/packages/examples/src/worker/ComWorkerCommon.ts index 510f685..6c8869c 100644 --- a/packages/examples/src/worker/ComWorkerCommon.ts +++ b/packages/examples/src/worker/ComWorkerCommon.ts @@ -1,7 +1,7 @@ -import { Payload, RawPayload } from 'wtd-core'; +import { OffscreenPayload } from 'wtd-three-ext'; -export const getOffScreenCanvas = (payload?: Payload) => { - return payload ? (payload as RawPayload).message.raw.drawingSurface as HTMLCanvasElement : undefined; +export const getOffScreenCanvas = (offScreenPayload?: OffscreenPayload) => { + return offScreenPayload ? offScreenPayload.message.drawingSurface as HTMLCanvasElement : undefined; }; export const updateText = (params: { diff --git a/packages/wtd-core/package.json b/packages/wtd-core/package.json index ddfaaee..b69a13a 100644 --- a/packages/wtd-core/package.json +++ b/packages/wtd-core/package.json @@ -1,6 +1,6 @@ { "name": "wtd-core", - "version": "3.0.0-next.0", + "version": "3.0.0-next.1", "license": "MIT", "type": "module", "main": "./dist/index.js", diff --git a/packages/wtd-core/src/WorkerTask.ts b/packages/wtd-core/src/WorkerTask.ts index 7c89caf..7696375 100644 --- a/packages/wtd-core/src/WorkerTask.ts +++ b/packages/wtd-core/src/WorkerTask.ts @@ -1,4 +1,5 @@ import type { + WorkerTaskCommands, WorkerTaskMessageConfig } from './WorkerTaskMessage.js'; import { @@ -197,9 +198,9 @@ export class WorkerTask { * @param message * @param transferables */ - sentMessage(message: WorkerTaskMessage, transferables?: Transferable[]) { + sentMessage(message: WorkerTaskMessage, cmd: WorkerTaskCommands, transferables?: Transferable[]) { if (this.isWorkerExecuting() && this.worker) { - message.cmd = WorkerTaskCommandRequest.INTERMEDIATE; + message.cmd = cmd; message.workerId = this.workerId; this.worker.postMessage(message, transferables!); } diff --git a/packages/wtd-core/src/WorkerTaskMessage.ts b/packages/wtd-core/src/WorkerTaskMessage.ts index b2ad381..00d1b2a 100644 --- a/packages/wtd-core/src/WorkerTaskMessage.ts +++ b/packages/wtd-core/src/WorkerTaskMessage.ts @@ -90,9 +90,9 @@ export class WorkerTaskMessage { return instance; } - static fromPayload(payload: Payload) { + static fromPayload(payloads: Payload | Payload[]) { const wtm = new WorkerTaskMessage({}); - wtm.addPayload(payload); + wtm.addPayload(payloads); return wtm; } } diff --git a/packages/wtd-three-ext/package.json b/packages/wtd-three-ext/package.json index f1ef198..1de0405 100644 --- a/packages/wtd-three-ext/package.json +++ b/packages/wtd-three-ext/package.json @@ -1,6 +1,6 @@ { "name": "wtd-three-ext", - "version": "3.0.0-next.0", + "version": "3.0.0-next.1", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -45,7 +45,7 @@ "npm": "9.8.1" }, "dependencies": { - "wtd-core": "~3.0.0-next.0", + "wtd-core": "~3.0.0-next.1", "three": "~0.158.0" }, "devDependencies": { diff --git a/packages/wtd-three-ext/src/index.ts b/packages/wtd-three-ext/src/index.ts index 9a97039..1e9ad73 100644 --- a/packages/wtd-three-ext/src/index.ts +++ b/packages/wtd-three-ext/src/index.ts @@ -30,7 +30,15 @@ import { packGeometryBuffers, reconstructBuffer } from './MeshPayload.js'; - +import type { + OffscreenPayloadAdditions, + OffscreenPayloadMessage +} from './offscreen/OffscreenPayload.js'; +import { + OffscreenPayload +} from './offscreen/OffscreenPayload.js'; +export * from './offscreen/EventProxy.js'; +export * from './offscreen/ProxyManager.js'; export { MaterialUtils, MaterialCloneInstructionsType, @@ -47,5 +55,8 @@ export { addAttributeToBuffers, assignAttributeFromTransfered, packGeometryBuffers, - reconstructBuffer + reconstructBuffer, + OffscreenPayloadAdditions, + OffscreenPayloadMessage, + OffscreenPayload, }; diff --git a/packages/wtd-three-ext/src/offscreen/EventProxy.ts b/packages/wtd-three-ext/src/offscreen/EventProxy.ts new file mode 100644 index 0000000..a5e2b3b --- /dev/null +++ b/packages/wtd-three-ext/src/offscreen/EventProxy.ts @@ -0,0 +1,138 @@ +import { AssociatedArrayType, WorkerTask, WorkerTaskMessage } from 'wtd-core'; +import { OffscreenPayload } from './OffscreenPayload.js'; + +export const wheelEventHandler = (event: Event, sendFn: (data: OffscreenPayload) => void) => { + event.preventDefault(); + wheelEventHandlerImpl(event, sendFn); +}; + +export const preventDefaultHandler = (event: Event) => { + event.preventDefault(); +}; + +export const copyProperties = (src: Event, properties: string[], dst: AssociatedArrayType) => { + for (const name of properties) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dst[name] = (src as any)[name]; + } +}; + +export const makeSendPropertiesHandler = (properties: string[]) => { + return function sendProperties(event: Event, sendFn: (data: OffscreenPayload) => void) { + const eventTarget = { + type: event.type, + }; + copyProperties(event, properties, eventTarget); + const payload = new OffscreenPayload({ + event: eventTarget + }); + + sendFn(payload); + }; +}; + +export const touchEventHandler = (event: TouchEvent, sendFn: (data: OffscreenPayload) => void) => { + const touches = []; + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < event.touches.length; ++i) { + const touch = event.touches[i]; + touches.push({ + pageX: touch.pageX, + pageY: touch.pageY, + }); + } + const payload = new OffscreenPayload({ + event: { + type: event.type, + touches + } + }); + sendFn(payload); +}; + +// The four arrow keys +export const orbitKeys: AssociatedArrayType = { + '37': true, // left + '38': true, // up + '39': true, // right + '40': true, // down +}; + +export const filteredKeydownEventHandler = (event: KeyboardEvent, sendFn: (data: OffscreenPayload) => void) => { + const { code } = event; + if (orbitKeys[code]) { + event.preventDefault(); + keydownEventHandler(event, sendFn); + } +}; + +export const mouseEventHandler = makeSendPropertiesHandler([ + 'ctrlKey', + 'metaKey', + 'shiftKey', + 'button', + 'clientX', + 'clientY', + 'pageX', + 'pageY', +]); + +export const wheelEventHandlerImpl = makeSendPropertiesHandler([ + 'deltaX', + 'deltaY', +]); + +export const keydownEventHandler = makeSendPropertiesHandler([ + 'ctrlKey', + 'metaKey', + 'shiftKey', + 'keyCode', +]); + +// function sendSize() { +// const rect = element.getBoundingClientRect(); +// sendEvent({ +// type: 'size', +// left: rect.left, +// top: rect.top, +// width: element.clientWidth, +// height: element.clientHeight, +// }); +// } + +// // really need to use ResizeObserver +// window.addEventListener('resize', sendSize); +// } + +export const registerCanvas = (canvas: HTMLCanvasElement, workerTask: WorkerTask) => { + canvas.focus(); + + const offscreenPayload = new OffscreenPayload({ + event: { + type: 'init' + } + }); + workerTask.sentMessage(WorkerTaskMessage.fromPayload(offscreenPayload), 'proxyStart'); + + const eventHandlers = { + contextmenu: preventDefaultHandler, + mousedown: mouseEventHandler, + mousemove: mouseEventHandler, + mouseup: mouseEventHandler, + touchstart: touchEventHandler, + touchmove: touchEventHandler, + touchend: touchEventHandler, + wheel: wheelEventHandler, + keydown: filteredKeydownEventHandler, + }; + + for (const [eventName, handler] of Object.entries(eventHandlers)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + canvas.addEventListener(eventName, function(event: any) { + handler(event, (offscreenPayload: OffscreenPayload) => { + workerTask.sentMessage(WorkerTaskMessage.fromPayload(offscreenPayload), 'proxyEvent'); + }); + }); + } +}; diff --git a/packages/wtd-three-ext/src/offscreen/OffscreenPayload.ts b/packages/wtd-three-ext/src/offscreen/OffscreenPayload.ts new file mode 100644 index 0000000..53a9c76 --- /dev/null +++ b/packages/wtd-three-ext/src/offscreen/OffscreenPayload.ts @@ -0,0 +1,24 @@ +import { AssociatedArrayType, Payload } from 'wtd-core'; + +export type OffscreenPayloadMessage = { + drawingSurface?: OffscreenCanvas | HTMLCanvasElement; + width?: number; + height?: number; + pixelRatio?: number; + top?: number; + left?: number; + event?: AssociatedArrayType; +} + +export type OffscreenPayloadAdditions = Payload & { + message: OffscreenPayloadMessage; +} + +export class OffscreenPayload implements OffscreenPayloadAdditions { + $type = 'OffscreenPayload'; + message: OffscreenPayloadMessage; + + constructor(message: OffscreenPayloadMessage) { + this.message = message; + } +} diff --git a/packages/wtd-three-ext/src/offscreen/ProxyManager.ts b/packages/wtd-three-ext/src/offscreen/ProxyManager.ts new file mode 100644 index 0000000..8728b36 --- /dev/null +++ b/packages/wtd-three-ext/src/offscreen/ProxyManager.ts @@ -0,0 +1,69 @@ +import { + EventDispatcher +} from 'three'; +import { OffscreenPayloadMessage } from './OffscreenPayload.js'; + +// const noop = () => { +// }; + +export class ElementProxyReceiver extends EventDispatcher { + private width: number = 0; + private height: number = 0; + private top: number = 0; + private left: number = 0; + + constructor() { + super(); + this.handleEvent = this.handleEvent.bind(this); + } + + get clientWidth() { + return this.width; + } + + get clientHeight() { + return this.height; + } + + getBoundingClientRect() { + return { + left: this.left, + top: this.top, + width: this.width, + height: this.height, + right: this.left + this.width, + bottom: this.top + this.height, + }; + } + + handleEvent(data: OffscreenPayloadMessage) { + if (data.event?.type === 'screen-size') { + this.left = data.left ?? 0; + this.top = data.top ?? 0; + this.width = data.width ?? 0; + this.height = data.height ?? 0; + return; + } + // data.preventDefault = noop; + // data.stopPropagation = noop; + this.dispatchEvent(data as never); + } + + focus() { + // no-op + } +} + +const proxy = new ElementProxyReceiver(); +export const proxyStart = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (proxy as any).body = proxy; // HACK! + // eslint-disable-next-line @typescript-eslint/no-explicit-any + self.window = proxy as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (self as any).document = { + addEventListener: proxy.addEventListener.bind(proxy), + removeEventListener: proxy.removeEventListener.bind(proxy), + }; +}; +