Skip to content

Commit 06de944

Browse files
committed
Better experimental_create_proxy
1 parent 4e5e892 commit 06de944

File tree

11 files changed

+243
-63
lines changed

11 files changed

+243
-63
lines changed

docs/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-1TWXRld0.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-1TWXRld0.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/pyodide.js

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { create } from 'gc-hook';
2-
3-
import { RUNNING_IN_WORKER, createProgress, writeFile } from './_utils.js';
1+
import { createProgress, writeFile } from './_utils.js';
42
import { getFormat, loader, loadProgress, registerJSModule, run, runAsync, runEvent } from './_python.js';
53
import { stdio } from './_io.js';
64
import { IDBMapSync, isArray, fixedRelative } from '../utils.js';
@@ -10,67 +8,57 @@ const toJsOptions = { dict_converter: Object.fromEntries };
108

119
const { stringify } = JSON;
1210

11+
const FunctionPrototype = Function.prototype;
12+
const { call } = FunctionPrototype;
13+
const apply = call.bind(call, call.apply);
14+
1315
// REQUIRES INTEGRATION TEST
1416
/* c8 ignore start */
15-
let overrideFunction = false;
16-
const overrideMethod = method => (...args) => {
17-
try {
18-
overrideFunction = true;
19-
return method(...args);
20-
}
21-
finally {
22-
overrideFunction = false;
23-
}
17+
const overrideMethod = method => function (...args) {
18+
return apply(method, this, args);
2419
};
2520

26-
let overridden = false;
27-
const applyOverride = () => {
28-
if (overridden) return;
29-
overridden = true;
21+
let pyproxy, to_js;
22+
const override = intercept => {
3023

3124
const proxies = new WeakMap;
32-
const onGC = value => value.destroy();
33-
const patchArgs = args => {
34-
for (let i = 0; i < args.length; i++) {
35-
const value = args[i];
36-
if (
37-
typeof value === 'function' &&
38-
'copy' in value
39-
) {
40-
// avoid seppuku / Harakiri + speed up
41-
overrideFunction = false;
42-
// reuse copied value if known already
43-
let proxy = proxies.get(value)?.deref();
44-
if (!proxy) {
45-
try {
46-
// observe the copy and return a Proxy reference
47-
proxy = create(value.copy(), onGC);
48-
proxies.set(value, new WeakRef(proxy));
49-
}
50-
catch (error) {
51-
console.error(error);
25+
26+
const patch = args => {
27+
for (let arg, i = 0; i < args.length; i++) {
28+
switch (typeof(arg = args[i])) {
29+
case 'object':
30+
if (arg === null) break;
31+
// falls through
32+
case 'function': {
33+
if (pyproxy in arg && !arg[pyproxy].shared?.gcRegistered) {
34+
intercept = false;
35+
let proxy = proxies.get(arg)?.deref();
36+
if (!proxy) {
37+
proxy = to_js(arg);
38+
const wr = new WeakRef(proxy);
39+
proxies.set(arg, wr);
40+
proxies.set(proxy, wr);
41+
}
42+
args[i] = proxy;
43+
intercept = true;
5244
}
45+
break;
5346
}
54-
if (proxy) args[i] = proxy;
55-
overrideFunction = true;
5647
}
5748
}
5849
};
5950

60-
// trap apply to make call possible after the patch
61-
const { call } = Function;
62-
const apply = call.bind(call, call.apply);
6351
// the patch
64-
Object.defineProperties(Function.prototype, {
52+
Object.defineProperties(FunctionPrototype, {
6553
apply: {
6654
value(context, args) {
67-
if (overrideFunction) patchArgs(args);
55+
if (intercept) patch(args);
6856
return apply(this, context, args);
6957
}
7058
},
7159
call: {
7260
value(context, ...args) {
73-
if (overrideFunction) patchArgs(args);
61+
if (intercept) patch(args);
7462
return apply(this, context, args);
7563
}
7664
}
@@ -85,9 +73,6 @@ export default {
8573
module: (version = '0.27.5') =>
8674
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
8775
async engine({ loadPyodide }, config, url, baseURL) {
88-
// apply override ASAP then load foreign code
89-
if (!RUNNING_IN_WORKER && config.experimental_create_proxy === 'auto')
90-
applyOverride();
9176
progress('Loading Pyodide');
9277
let { packages, index_urls } = config;
9378
if (packages) packages = packages.map(fixedRelative, baseURL);
@@ -134,13 +119,30 @@ export default {
134119
await storage.close();
135120
if (options.lockFileURL) URL.revokeObjectURL(options.lockFileURL);
136121
progress('Loaded Pyodide');
122+
if (config.experimental_create_proxy === 'auto') {
123+
interpreter.runPython([
124+
'import js',
125+
'from pyodide.ffi import to_js',
126+
'o=js.Object.fromEntries',
127+
'js.experimental_create_proxy=lambda r:to_js(r,dict_converter=o)'
128+
].join(';'), { globals: interpreter.toPy({}) });
129+
to_js = globalThis.experimental_create_proxy;
130+
delete globalThis.experimental_create_proxy;
131+
[pyproxy] = Reflect.ownKeys(to_js).filter(
132+
k => (
133+
typeof k === 'symbol' &&
134+
String(k) === 'Symbol(pyproxy.attrs)'
135+
)
136+
);
137+
override(true);
138+
}
137139
return interpreter;
138140
},
139141
registerJSModule,
140142
run: overrideMethod(run),
141143
runAsync: overrideMethod(runAsync),
142144
runEvent: overrideMethod(runEvent),
143-
transform: (interpreter, value) => transform.call(interpreter, value),
145+
transform: (interpreter, value) => apply(transform, interpreter, [value]),
144146
writeFile: (interpreter, path, buffer, url) => {
145147
const format = getFormat(path, url);
146148
if (format) {

esm/interpreter/webr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const run = async (interpreter, code) => {
2121
export default {
2222
type,
2323
experimental: true,
24-
module: (version = '0.4.2') =>
24+
module: (version = '0.4.3') =>
2525
`https://cdn.jsdelivr.net/npm/webr@${version}/dist/webr.mjs`,
2626
async engine(module, config, _, baseURL) {
2727
const { get } = stdio();

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@playwright/test": "^1.52.0",
5151
"@rollup/plugin-node-resolve": "^16.0.1",
5252
"@rollup/plugin-terser": "^0.4.4",
53-
"@zip.js/zip.js": "^2.7.60",
53+
"@zip.js/zip.js": "^2.7.61",
5454
"c8": "^10.1.3",
5555
"chokidar": "^4.0.3",
5656
"eslint": "^9.26.0",
@@ -88,14 +88,14 @@
8888
"@webreflection/utils": "^0.1.0",
8989
"basic-devtools": "^0.1.6",
9090
"codedent": "^0.1.2",
91-
"coincident": "^3.0.4",
91+
"coincident": "^3.0.5",
9292
"gc-hook": "^0.4.1",
9393
"html-escaper": "^3.0.3",
9494
"proxy-target": "^3.0.2",
9595
"sticky-module": "^0.1.1",
9696
"to-json-callback": "^0.1.1"
9797
},
9898
"worker": {
99-
"blob": "sha256-xlgyKcR1rQj2E2TB4Pp07IZjwgkIyN9uf8HfcuUXwro="
99+
"blob": "sha256-pPnaQgTCECMn3JYzEii3JVJ7VnQ0w2QmEt/Phiz1/Oo="
100100
}
101101
}

test/create_proxy/index.html

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>dc.js example</title>
5+
<!-- original jsfiddle https://jsfiddle.net/gordonwoodhull/5ztavmjy/2 -->
6+
<meta charset="UTF-8">
7+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
8+
<style>pre#data{display:none}</style>
9+
<script type="module">
10+
import { define } from "../../dist/index.js";
11+
define("py", {
12+
interpreter: "pyodide",
13+
config: {
14+
experimental_create_proxy: "auto",
15+
js_modules: {
16+
main: {
17+
"https://unpkg.com/d3?module" : "d3",
18+
"https://unpkg.com/dc?module" : "dc",
19+
"https://unpkg.com/crossfilter2?module" : "crossfilter"
20+
}
21+
}
22+
},
23+
hooks: {
24+
main: {
25+
onReady(wrap, element) {
26+
fetch(element.src).then(b => b.text()).then(wrap.run);
27+
}
28+
}
29+
}
30+
});
31+
</script>
32+
</head>
33+
<body>
34+
<script type="py" src="./main.py" config="./pyscript.toml"></script>
35+
<div id="test"></div>
36+
<!-- here's a way to load data from a jsfiddle, to avoid CORS
37+
problems - see http://stackoverflow.com/a/22896088/676195
38+
-->
39+
<pre id="data">
40+
Expt,Run,Speed
41+
1,1,850
42+
1,2,740
43+
1,3,900
44+
1,4,1070
45+
1,5,930
46+
1,6,850
47+
1,7,950
48+
1,8,980
49+
1,9,980
50+
1,10,880
51+
1,11,1000
52+
1,12,980
53+
1,13,930
54+
1,14,650
55+
1,15,760
56+
1,16,810
57+
1,17,1000
58+
1,18,1000
59+
1,19,960
60+
1,20,960
61+
2,1,960
62+
2,2,940
63+
2,3,960
64+
2,4,940
65+
2,5,880
66+
2,6,800
67+
2,7,850
68+
2,8,880
69+
2,9,900
70+
2,10,840
71+
2,11,830
72+
2,12,790
73+
2,13,810
74+
2,14,880
75+
2,15,880
76+
2,16,830
77+
2,17,800
78+
2,18,790
79+
2,19,760
80+
2,20,800
81+
3,1,880
82+
3,2,880
83+
3,3,880
84+
3,4,860
85+
3,5,720
86+
3,6,720
87+
3,7,620
88+
3,8,860
89+
3,9,970
90+
3,10,950
91+
3,11,880
92+
3,12,910
93+
3,13,850
94+
3,14,870
95+
3,15,840
96+
3,16,840
97+
3,17,850
98+
3,18,840
99+
3,19,840
100+
3,20,840
101+
4,1,890
102+
4,2,810
103+
4,3,810
104+
4,4,820
105+
4,5,800
106+
4,6,770
107+
4,7,760
108+
4,8,740
109+
4,9,750
110+
4,10,760
111+
4,11,910
112+
4,12,920
113+
4,13,890
114+
4,14,860
115+
4,15,880
116+
4,16,720
117+
4,17,840
118+
4,18,850
119+
4,19,850
120+
4,20,780
121+
5,1,890
122+
5,2,840
123+
5,3,780
124+
5,4,810
125+
5,5,760
126+
5,6,810
127+
5,7,790
128+
5,8,810
129+
5,9,820
130+
5,10,850
131+
5,11,870
132+
5,12,870
133+
5,13,810
134+
5,14,740
135+
5,15,810
136+
5,16,940
137+
5,17,950
138+
5,18,800
139+
5,19,810
140+
5,20,870
141+
</pre>
142+
</body>
143+
</html>

0 commit comments

Comments
 (0)