Skip to content

Commit

Permalink
Breaking: refactory of all main/worker hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
WebReflection committed Oct 24, 2023
1 parent dcad916 commit b6e9143
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 138 deletions.
4 changes: 2 additions & 2 deletions docs/core.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/core.js.map

Large diffs are not rendered by default.

114 changes: 60 additions & 54 deletions esm/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { registry as defaultRegistry, prefixes, configs } from './interpreters.j
import { getRuntimeID } from './loader.js';
import { io } from './interpreter/_utils.js';
import { addAllListeners } from './listeners.js';
import { main, worker } from './hooks.js';
import { Hook, XWorker } from './xworker.js';
import workerURL from './worker/url.js';

Expand Down Expand Up @@ -44,8 +45,8 @@ export const handleCustomType = (node) => {
config,
version,
env,
onInterpreterReady,
onerror,
hooks,
} = options;

let error;
Expand Down Expand Up @@ -83,52 +84,51 @@ export const handleCustomType = (node) => {
engine.then((interpreter) => {
const module = create(defaultRegistry.get(runtime));

const {
onBeforeRun,
onBeforeRunAsync,
onAfterRun,
onAfterRunAsync,
} = options;

const hooks = new Hook(interpreter, options);
const hook = new Hook(interpreter, options);

const XWorker = function XWorker(...args) {
return Worker.apply(hooks, args);
return Worker.apply(hook, args);
};

// These two loops mimic a `new Map(arrayContent)` without needing
// the new Map overhead so that [name, [before, after]] can be easily destructured
// and new sync or async patches become easy to add (when the logic is the same).

// patch sync
for (const [name, [before, after]] of [
['run', [onBeforeRun, onAfterRun]],
]) {
const method = module[name];
module[name] = function (interpreter, code, ...args) {
if (before) before.call(this, resolved, node);
const result = method.call(this, interpreter, code, ...args);
if (after) after.call(this, resolved, node);
return result;
};
}

// patch async
for (const [name, [before, after]] of [
['runAsync', [onBeforeRunAsync, onAfterRunAsync]],
]) {
const method = module[name];
module[name] = async function (interpreter, code, ...args) {
if (before) await before.call(this, resolved, node);
const result = await method.call(
this,
interpreter,
code,
...args
);
if (after) await after.call(this, resolved, node);
return result;
};
// patch methods accordingly to hooks (and only if needed)
for (const suffix of ['Run', 'RunAsync']) {
let before, after;
// ignore onReady, onWorker and all other worker-like hooks around code,
// as that will be implemented later.
// TODO: implement code snippets for custom types too!
for (let i = worker.length + 1; i < main.length; i++) {
const key = main[i];
const value = hooks?.main?.[key];
if (value && key.endsWith(suffix)) {
if (key.startsWith('onBefore'))
before = value;
else
after = value;
}
}
if (before || after) {
const name = `r${suffix.slice(1)}`;
const method = module[name];
module[name] = suffix.endsWith('Async') ?
async function (interpreter, code, ...args) {
if (before) await before.call(this, resolved, node);
const result = await method.call(
this,
interpreter,
code,
...args
);
if (after) await after.call(this, resolved, node);
return result;
} :
function (interpreter, code, ...args) {
if (before) before.call(this, resolved, node);
const result = method.call(this, interpreter, code, ...args);
if (after) after.call(this, resolved, node);
return result;
}
;
}
}

module.registerJSModule(interpreter, 'polyscript', { XWorker });
Expand All @@ -147,7 +147,7 @@ export const handleCustomType = (node) => {
details.queue = details.queue.then(() => {
resolve(resolved);
if (error) onerror?.(error, node);
return onInterpreterReady?.(resolved, node);
return hooks?.main?.onReady?.(resolved, node);
});
});
}
Expand All @@ -165,7 +165,6 @@ const registry = new Map();
* @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
* @prop {string} [version] the optional interpreter version to use
* @prop {string} [config] the optional config to use within such interpreter
* @prop {(environment: object, node: Element) => void} [onInterpreterReady] the callback that will be invoked once
*/

let dontBotherCount = 0;
Expand Down Expand Up @@ -198,17 +197,24 @@ export const define = (type, options) => {

if (dontBother) {
// add a script then cleanup everything once that's ready
const { onInterpreterReady } = options;
const { hooks } = options;
const onReady = hooks?.main?.onReady;
options = {
...options,
onInterpreterReady(resolved, node) {
CUSTOM_SELECTORS.splice(CUSTOM_SELECTORS.indexOf(type), 1);
defaultRegistry.delete(type);
registry.delete(type);
waitList.delete(type);
node.remove();
onInterpreterReady?.(resolved);
}
hooks: {
...hooks,
main: {
...hooks?.main,
onReady(resolved, node) {
CUSTOM_SELECTORS.splice(CUSTOM_SELECTORS.indexOf(type), 1);
defaultRegistry.delete(type);
registry.delete(type);
waitList.delete(type);
node.remove();
onReady?.(resolved);
}
}
},
};
document.head.append(
assign(document.createElement('script'), { type })
Expand Down
19 changes: 19 additions & 0 deletions esm/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const beforeRun = 'BeforeRun';
const afterRun = 'AfterRun';

export const worker = [
'onReady',
`code${beforeRun}`,
`code${beforeRun}Async`,
`code${afterRun}`,
`code${afterRun}Async`,
];

export const main = [
...worker,
'onWorker',
`on${beforeRun}`,
`on${beforeRun}Async`,
`on${afterRun}`,
`on${afterRun}Async`,
];
42 changes: 27 additions & 15 deletions esm/worker/_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import coincident from 'coincident/window';
import { assign, create, dispatch } from '../utils.js';
import { registry } from '../interpreters.js';
import { getRuntime, getRuntimeID } from '../loader.js';
import { worker } from '../hooks.js';

// bails out out of the box with a native/meaningful error
// in case the SharedArrayBuffer is not available
Expand Down Expand Up @@ -76,26 +77,37 @@ add('message', ({ data: { options, config: baseURL, code, hooks } }) => {
const name = `run${isAsync ? 'Async' : ''}`;

if (hooks) {
// patch code if needed
const { beforeRun, beforeRunAsync, afterRun, afterRunAsync } =
hooks;

const after = isAsync ? afterRunAsync : afterRun;
const before = isAsync ? beforeRunAsync : beforeRun;

// append code that should be executed *after* first
if (after) {
const overload = ($, pre) => {
const method = details[name].bind(details);
details[name] = (interpreter, code, ...args) =>
method(interpreter, `${code}\n${after}`, ...args);
method(interpreter, `${pre ? $ : code}\n${pre ? code : $}`, ...args);
};

let before = '';
let after = '';

// exclude `onReady` hook
for (const key of worker.slice(1)) {
const value = hooks[key];
if (value) {
const asyncCode = key.endsWith('Async');
// either async hook and this worker is async
// or sync hook and this worker is sync
// other shared options possible cases are ignored
if ((asyncCode && isAsync) || (!asyncCode && !isAsync)) {
if (key.startsWith('codeBefore'))
before = value;
else
after = value;
}
}
}

// append code that should be executed *after* first
if (after) overload(after, false);

// prepend code that should be executed *before* (so that after is post-patched)
if (before) {
const method = details[name].bind(details);
details[name] = (interpreter, code, ...args) =>
method(interpreter, `${before}\n${code}`, ...args);
}
if (before) overload(before, true);
}

const { CustomEvent, document } = window;
Expand Down
8 changes: 4 additions & 4 deletions esm/worker/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import xworker from './xworker.js';
import { getConfigURLAndType } from '../loader.js';
import { assign, create, defineProperties } from '../utils.js';
import { getText } from '../fetch-utils.js';
import { Hook } from './hooks.js';
import Hook from './hook.js';

/**
* @typedef {Object} WorkerOptions custom configuration
Expand Down Expand Up @@ -39,7 +39,7 @@ export default (...args) =>
const bootstrap = fetch(url)
.then(getText)
.then(code => {
const hooks = isHook ? this.stringHooks : void 0;
const hooks = isHook ? this.toJSON() : void 0;
postMessage.call(worker, { options, config, code, hooks });
});

Expand All @@ -60,8 +60,6 @@ export default (...args) =>
}
});

if (isHook) this.onWorkerReady?.(this.interpreter, worker);

worker.addEventListener('message', event => {
const { data } = event;
if (data instanceof Error) {
Expand All @@ -73,5 +71,7 @@ export default (...args) =>
}
});

if (isHook) this.onWorker?.(this.interpreter, worker);

return worker;
};
22 changes: 22 additions & 0 deletions esm/worker/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { dedent } from '../utils.js';
import { worker } from '../hooks.js';

// REQUIRES INTEGRATION TEST
/* c8 ignore start */
export default class Hook {
constructor(interpreter, { hooks }) {
this.interpreter = interpreter;
this.onWorker = hooks?.main?.onWorker;
for (const key of worker)
this[key] = hooks?.worker?.[key];
}
toJSON() {
const hooks = {};
// exclude `onReady` callback
for (const key of worker.slice(1)) {
if (this[key]) hooks[key] = dedent(this[key]());
}
return hooks;
}
}
/* c8 ignore stop */
27 changes: 0 additions & 27 deletions esm/worker/hooks.js

This file was deleted.

2 changes: 1 addition & 1 deletion esm/xworker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import xworker from './worker/class.js';
import { Hook } from './worker/hooks.js';
import Hook from './worker/hook.js';

const XWorker = xworker();

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,6 @@
"sticky-module": "^0.1.0"
},
"worker": {
"blob": "sha256-U4v8Z3y75f9H4l1rpfK2fvxuGhHqh/XNRPPmwEx08kg="
"blob": "sha256-o8yMeYkBjacr/D38guZaXXBLIXjPV/zcvnODdd9k13M="
}
}
Loading

0 comments on commit b6e9143

Please sign in to comment.