sabayon
Version:
SharedArrayBuffer always on
150 lines (126 loc) • 3.97 kB
JavaScript
// (c) Andrea Giammarchi - MIT
const ACTION_INIT = 0;
const ACTION_NOTIFY = 1;
const ACTION_WAIT = 2;
const ACTION_SW = 3;
const { ArrayBuffer, Atomics: $Atomics, Promise } = globalThis;
const { isArray } = Array;
const { create, getPrototypeOf, values } = Object;
const TypedArray = getPrototypeOf(Int32Array);
const Atomics = /** @type {globalThis.Atomics} */(create($Atomics));
const dispatch = ({ currentTarget, type, origin, lastEventId, source, ports }, data) =>
currentTarget.dispatchEvent(new MessageEvent(type, { data, origin, lastEventId, source, ports }));
const withResolvers = () => Promise.withResolvers();
let id = 0;
const views = new Map;
const extend = (Class, SharedArrayBuffer) => class extends Class {
constructor(value, ...rest) {
super(value, ...rest);
if (value instanceof SharedArrayBuffer)
views.set(this, [id++, 0, withResolvers()]);
}
};
const ignoreList = new WeakSet;
/**
* @template {T}
* @callback PassThrough
* @param {T} value
* @returns {T}
*/
/** @type {PassThrough} */
const ignoreDirect = value => value;
/** @type {PassThrough} */
const ignorePatch = value => {
ignoreList.add(value);
return value;
};
const isChannel = (event, channel) => {
const { data } = event;
const yes = isArray(data) && (
data.at(0) === channel ||
(data.at(1) === ACTION_INIT && !channel)
);
if (yes) {
event.stopImmediatePropagation();
event.preventDefault();
}
return yes;
};
const isObject = value => (
value !== null &&
typeof value === 'object' &&
!ignoreList.has(value)
);
const transferred = new WeakMap;
const transferViews = (data, transfer, visited) => {
if (views.has(data))
transfer.set(data, views.get(data)[0]);
else if (!(data instanceof TypedArray || data instanceof ArrayBuffer)) {
for (const value of values(data)) {
if (isObject(value) && !visited.has(value)) {
visited.add(value);
transferViews(value, transfer, visited);
}
}
}
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync#browser_compatibility
let intAsyncPatch = 0;
const waitAsyncPatch = (...args) => ({
value: new Promise(async resolve => {
// fallback to a local file to avoid CSP issues
// this file must contain: onmessage=e=>postMessage(!Atomics.wait(...e.data))
const url = '/__sabayon_wait_async.js';
const data = 'data:application/javascript,onmessage%3De%3D%3EpostMessage(!Atomics.wait(...e.data))';
// perform this check only once
if (!intAsyncPatch) {
const ok = fetch(url, { method: 'HEAD' }).then(r => r.ok, () => false);
intAsyncPatch = (await ok) ? 1 : -1;
}
const worker = new Worker(intAsyncPatch < 0 ? data : url);
worker.onmessage = () => {
worker.terminate();
resolve('ok');
};
worker.postMessage(args);
})
});
const waitAsyncPoly = (view, index) => {
const value = views.get(view), [id, _, { promise }] = value;
value[1] = index;
return [id, promise];
};
const actionNotify = (_view, _id, _index) => {
for (const [view, [id, index, { resolve }]] of views) {
if (_id === id && _index === index) {
view.set(_view);
views.delete(view);
resolve('ok');
break;
}
}
};
const actionWait = (event, transfer, data) => {
for (const [view, id] of transfer)
transferred.set(view, [id, event.currentTarget]);
dispatch(event, data);
};
const postData = (CHANNEL, data) => {
const transfer = new Map;
if (isObject(data)) transferViews(data, transfer, new Set);
return transfer.size ? [CHANNEL, ACTION_WAIT, transfer, data] : data;
};
const getData = view => transferred.get(view);
export {
ACTION_INIT, ACTION_NOTIFY, ACTION_WAIT, ACTION_SW,
ArrayBuffer, Atomics, TypedArray,
actionNotify, actionWait,
getData, postData,
ignoreDirect, ignorePatch,
waitAsyncPatch, waitAsyncPoly,
dispatch,
extend,
isChannel,
views,
withResolvers,
};