-
Notifications
You must be signed in to change notification settings - Fork 2
/
webworker.js
194 lines (180 loc) · 7.21 KB
/
webworker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*https://pyodide.org/en/stable/usage/webworker.html*/
importScripts("https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js");
/* ___________________________________________ DEFERRED PROMISE _________________________________________________ */
class DeferredPromise{
/* this is an object which creates a promise in a different spot from where it will be resolve. It allows you
to say wait on athe completion of a task that you have not yet started. The interface is similar to
that of a promise (supports `then`, `catch` and if you wish you can access the true promise with
the `promise` attribute.*/
constructor(){
this.fulfilled = false
this.rejected = false
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
this._resolve = ()=>{}
this._reject = ()=>{}
this.promise = new Promise((resolve, reject)=>{
this._resolve = resolve
this._reject = reject
})
}
resolve(r){
this.fulfilled=true;
this.result = r;
this._resolve(r);
}
reject(e){
this._reject(e)
this.rejected=true;
this.error = e;
}
then(onfulfilled, onrejected){return this.promise.then(onfulfilled, onrejected)}
catch(onrejected){return this.promise.catch(onrejected)}
}
/* ___________________________________________ INTERFACE _________________________________________________ */
let workerReady = new DeferredPromise();
let pyodidePromise = new DeferredPromise();
class Interface{
constructor(){
this.parent = self;
// bind methods to scope, may be unnecessary
this.getMethod = this.getMethod.bind(this);
this.postResponse = this.postResponse.bind(this);
this.postError = this.postError.bind(this);
this.postRequest = this.postRequest.bind(this);
this.postCall = this.postCall.bind(this);
this.receiveResponse = this.receiveResponse.bind(this);
this.receiveCallRequest = this.receiveCallRequest.bind(this);
this.receiveRequest = this.receiveRequest.bind(this);
this.runPythonAsyncInNamespace = this.runPythonAsyncInNamespace.bind(this);
this.getNamespace = this.getNamespace.bind(this)
this.stdout = this.stdout.bind(this);
this.stderr = this.stderr.bind(this);
this.worker = this;
this.proxy = new Proxy(this, {
get(target, prop, receiver) {
if (target[prop] !== undefined){return target[prop]}
function callMethod(){
return target.postRequest(prop, Array.from(arguments))
}
return callMethod
}
});
workerReady.resolve(this);
return this.proxy
}
_id = 0
get id(){
let id = this._id
this._id = (id + 1) % Number.MAX_SAFE_INTEGER;
return id
}
pendingRequests = {}
receivemessage(event){
console.warn("received", event.data)
let data = Object.assign({}, event.data)
let type = data.type
if (type === "response"){this.receiveResponse(data)}
else if (type === "call"){this.receiveCallRequest(data)}
else if (type === "request"){this.receiveRequest(data)}
else{this.postError(data, "unrecognized type")}
}
getMethod(methodName, scopes){
if (!scopes){scopes = [this.parent, this]}
for (let scope of scopes){
if (scope[methodName]){return scope[methodName]}
else if (methodName.includes(".")){
let methodNames = methodName.split(".")
for (let mn of methodNames){
scope = scope[mn]
if (!scope){return scope}
}
return scope
}
}
}
postResponse(data, results, error=null){
data.type = "response";
data.results = results;
data.error = error;
this.postMessage(data)
}
postError(data, error){this.postResponse(data, null, error)}
postRequest(method, args, type="request"){
let id = this.id;
let data = {id, type, method, args, results: null, error: null}
this.pendingRequests[id] = [new DeferredPromise(), data];
this.postMessage(data)
return this.pendingRequests[id][0]
}
postCall(method, args){this.postRequest(method, args, "call")}
receiveResponse(data){
let [deferredPromise, sentData] = this.pendingRequests[data.id];
delete this.pendingRequests[data.id];
if (data.results){deferredPromise.resolve(data.results)}
else{ deferredPromise.reject(data.error)}
}
receiveCallRequest(data){
let f = this.getMethod(data.method);
console.warn("F", f)
if (f){ return f(...data.args)}
else{this.postError(data, "method not found:" + self.pyodide)}
}
receiveRequest(data){
try{
let results = this.receiveCallRequest(data);
if (results.then){
results
.then(r=>this.postResponse(data, r))
.catch(e=>this.postError(data, e))
}
else{this.postResponse(data, results)}
}
catch(error){this.postError(data, error)}
}
stdout(...args){
console.log(args)
}
stderr(...args){
console.log(args)
}
stdin(){return ""}
namespaces = {}
getNamespace(name){
if (this.namespaces[name] === undefined){
this.namespaces[name] = pyodide.globals.get("dict")();
}
return this.namespaces[name]
}
runPythonAsyncInNamespace(code, namespace){
return pyodide.runPythonAsync(code, {globals: this.getNamespace(namespace)})
}
}
self.interface = new Interface();
self.interface.postMessage = (data)=>{self.postMessage(data)}
self.onmessage = async (event) => {
self.interface.receivemessage(event)
}
/* ___________________________________________ REST _________________________________________________ */
self.stdout = (message)=>{self.interface.postCall("stdout", [message])}
self.stderr = (message)=>{self.interface.postCall("stderr", [message])}
self.stdin = ()=>{self.interface.postRequest("stdin", [])}
function loadPyodideAndPackages() {
return loadPyodide({
stdout: self.stdout,
stderr: self.stderr,
stdin: self.stdin,
}).then(pyodide=>{
self.pyodide = pyodide;
self.interface.parent = self.pyodide;
pyodide.runPythonAsync(`
def try_to_input(msg):
print(msg)
raise Exception('input function not implemented in webworker, please specify webworker="false"')
`).then(r=>{
pyodidePromise.resolve(pyodide);
self.postMessage({id: -1, type:"call", method:"workerReady.resolve", args: [], results: false, error: null})
})
})
}
loadPyodideAndPackages();