coincident
Version:
An Atomics based Proxy to simplify, and synchronize, Worker related tasks
152 lines (138 loc) • 4.7 kB
JavaScript
import {
APPLY,
DEFINE_PROPERTY,
GET,
GET_OWN_PROPERTY_DESCRIPTOR,
OWN_KEYS,
DESTRUCT,
} from 'js-proxy/traps';
import {
ARRAY,
FUNCTION,
NULL,
NUMBER,
OBJECT,
STRING,
SYMBOL,
UNDEFINED,
} from 'js-proxy/types';
import DEBUG from '../debug.js';
import { DESTROY } from './traps.js';
import { create as heap } from 'js-proxy/heap';
import { TypedArray } from 'sabayon/shared';
import numeric from '../window/types.js';
import { fromSymbol, toSymbol } from '../window/symbol.js';
import { create } from 'gc-hook';
import handleEvent from '../window/events.js';
const { isArray } = Array;
export default (resolve, __worker__) => {
const { clear, drop, get, hold } = heap();
const proxies = new Map;
const onGC = ref => {
if (DEBUG) console.info('main collecting', ref);
proxies.delete(ref);
__worker__(DESTRUCT, ref);
}
const toEntry = value => {
const TYPE = typeof value;
if (DEBUG) console.log('toEntry', TYPE, TYPE === 'function' ? value.name : '');
switch (TYPE) {
case OBJECT: {
if (value === null) return [numeric[NULL], value];
if (value === globalThis) return [numeric[OBJECT], null];
if (isArray(value)) return [numeric[ARRAY], hold(value)];
return [numeric[OBJECT], value instanceof TypedArray ? value : hold(value)];
}
case FUNCTION: return [numeric[FUNCTION], hold(value)];
case SYMBOL: return [numeric[SYMBOL], toSymbol(value)];
default: return [numeric[TYPE], value];
}
};
const fromEntry = ([numericTYPE, value]) => {
switch (numericTYPE) {
case numeric[OBJECT]: {
if (value === null) return globalThis;
if (typeof value === NUMBER) return get(value);
if (!(value instanceof TypedArray)) {
for (const key in value)
value[key] = fromEntry(value[key]);
}
return value;
};
case numeric[ARRAY]: {
if (typeof value === NUMBER) return get(value);
return value.map(fromEntry);
};
case numeric[FUNCTION]: {
switch (typeof value) {
case NUMBER: return get(value);
case STRING: {
let fn = proxies.get(value)?.deref();
if (!fn) {
fn = create(value, onGC, {
token: false,
return: function (...args) {
if (args.length && args[0] instanceof Event) handleEvent(args[0]);
return __worker__(APPLY, value, toEntry(this), args.map(toEntry)).then(fromEntry);
}
});
proxies.set(value, new WeakRef(fn));
}
return fn;
}
}
};
case numeric[SYMBOL]: return fromSymbol(value);
default: return value;
}
};
const asEntry = (method, target, args) => toEntry(method(target, ...args.map(fromEntry)));
const asImport = name => import(resolve(name));
const globals = new Set;
return (TRAP, ref, ...args) => {
if (TRAP === DESTROY) {
if (DEBUG) console.warn('HEAP erased');
clear();
}
else if (TRAP === DESTRUCT) {
if (DEBUG) console.info(`Main ${globals.has(ref) ? 'ignoring' : 'dropping'}`, ref);
if (!globals.has(ref)) drop(ref);
}
else {
const method = Reflect[TRAP];
const target = ref == null ? globalThis : get(ref);
switch (TRAP) {
case DEFINE_PROPERTY: {
const [name, descriptor] = args.map(fromEntry);
return toEntry(method(target, name, descriptor));
}
case GET_OWN_PROPERTY_DESCRIPTOR: {
const descriptor = method(target, ...args.map(fromEntry));
if (descriptor) {
const { get, set, value } = descriptor;
if (get) descriptor.get = toEntry(get);
if (set) descriptor.set = toEntry(set);
if (value) descriptor.value = toEntry(value);
}
return [numeric[descriptor ? OBJECT : UNDEFINED], descriptor];
}
case OWN_KEYS: return [numeric[ARRAY], method(target).map(toEntry)];
case GET: {
// global references should not be proxied more than once
// and should not ever be dropped from the main cache
// @see https://github.com/pyscript/pyscript/issues/2185
if (ref == null) {
const result = args[0][1] === 'import' ?
toEntry(asImport) :
asEntry(method, target, args)
;
globals.add(result[1]);
if (DEBUG) console.info('Global', args[0][1], result[1]);
return result;
}
}
default: return asEntry(method, target, args);
}
}
};
};