webgpu 0.0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webgpu/__init__.py +12 -0
- webgpu/camera.py +191 -0
- webgpu/canvas.py +144 -0
- webgpu/clipping.py +140 -0
- webgpu/colormap.py +327 -0
- webgpu/draw.py +35 -0
- webgpu/font.py +164 -0
- webgpu/fonts.json +52 -0
- webgpu/gpu.py +191 -0
- webgpu/input_handler.py +81 -0
- webgpu/jupyter.py +161 -0
- webgpu/jupyter_pyodide.py +365 -0
- webgpu/labels.py +134 -0
- webgpu/light.py +12 -0
- webgpu/lilgui.py +75 -0
- webgpu/link/__init__.py +3 -0
- webgpu/link/base.py +439 -0
- webgpu/link/link.js +431 -0
- webgpu/link/proxy.py +81 -0
- webgpu/link/websocket.py +115 -0
- webgpu/main.py +177 -0
- webgpu/platform.py +125 -0
- webgpu/render_object.py +159 -0
- webgpu/scene.py +206 -0
- webgpu/shaders/__init__.py +0 -0
- webgpu/shaders/camera.wgsl +21 -0
- webgpu/shaders/clipping.wgsl +35 -0
- webgpu/shaders/colormap.wgsl +60 -0
- webgpu/shaders/font.wgsl +53 -0
- webgpu/shaders/light.wgsl +9 -0
- webgpu/shaders/text.wgsl +57 -0
- webgpu/shaders/triangulation.wgsl +34 -0
- webgpu/shaders/vector.wgsl +118 -0
- webgpu/triangles.py +68 -0
- webgpu/uniforms.py +111 -0
- webgpu/utils.py +385 -0
- webgpu/vectors.py +103 -0
- webgpu/webgpu_api.py +1759 -0
- webgpu-0.0.1.dev0.dist-info/METADATA +33 -0
- webgpu-0.0.1.dev0.dist-info/RECORD +43 -0
- webgpu-0.0.1.dev0.dist-info/WHEEL +5 -0
- webgpu-0.0.1.dev0.dist-info/licenses/LICENSE +504 -0
- webgpu-0.0.1.dev0.dist-info/top_level.txt +1 -0
webgpu/link/link.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
function serializeEvent(event) {
|
|
4
|
+
event.preventDefault();
|
|
5
|
+
const keys = [
|
|
6
|
+
'button',
|
|
7
|
+
'altKey',
|
|
8
|
+
'metaKey',
|
|
9
|
+
'ctrlKey',
|
|
10
|
+
'shiftKey',
|
|
11
|
+
'x',
|
|
12
|
+
'y',
|
|
13
|
+
'deltaX',
|
|
14
|
+
'deltaY',
|
|
15
|
+
'deltaMode',
|
|
16
|
+
'movementX',
|
|
17
|
+
'movementY',
|
|
18
|
+
];
|
|
19
|
+
return Object.fromEntries(keys.map((k) => [k, event[k]]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function decodeB64(data) {
|
|
23
|
+
return Uint8Array.from(atob(data), (c) => c.charCodeAt(0)).buffer;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function encodeB64(buffer) {
|
|
27
|
+
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isPrimitive(value) {
|
|
31
|
+
return (
|
|
32
|
+
value === null || (typeof value !== 'object' && typeof value !== 'function')
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isPrimitiveDict(obj) {
|
|
37
|
+
if (obj.$children) return false;
|
|
38
|
+
if (obj === window) return false;
|
|
39
|
+
if (obj === navigator) return false;
|
|
40
|
+
|
|
41
|
+
if (typeof obj !== 'object' || obj === null) return false;
|
|
42
|
+
if (Array.isArray(obj)) {
|
|
43
|
+
return obj.every(isPrimitiveDict);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (let key in obj) {
|
|
47
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
48
|
+
const value = obj[key];
|
|
49
|
+
|
|
50
|
+
if (!isPrimitive(value) && !isPrimitiveDict(value)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (obj.constructor !== Object) return false;
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createProxy(link, id, parent_id) {
|
|
62
|
+
const target = function (...args) {
|
|
63
|
+
return link.call(id, args, parent_id);
|
|
64
|
+
};
|
|
65
|
+
target.handleEvent = async (eventType, event) => {
|
|
66
|
+
return link.call(id, [eventType, event], parent_id, '_handle');
|
|
67
|
+
};
|
|
68
|
+
target.callFunction = (self, arg) => {
|
|
69
|
+
return link.call(id, [arg], parent_id);
|
|
70
|
+
};
|
|
71
|
+
target.callMethod = async (prop, arg) => {
|
|
72
|
+
return await link.callMethod(id, prop, [arg]);
|
|
73
|
+
};
|
|
74
|
+
target.callMethodIgnoreResult = (prop, arg) => {
|
|
75
|
+
link.callMethodIgnoreResult(id, prop, [arg]);
|
|
76
|
+
};
|
|
77
|
+
const handler = {
|
|
78
|
+
get: function (obj, prop) {
|
|
79
|
+
if (prop === '_parent_id') return parent_id;
|
|
80
|
+
if (prop === '_id') return id;
|
|
81
|
+
if (prop === '_link') return link;
|
|
82
|
+
if (prop === 'handleEvent') return target.handleEvent;
|
|
83
|
+
if (prop === 'call') {
|
|
84
|
+
return target.callFunction;
|
|
85
|
+
}
|
|
86
|
+
if (prop === 'then') return Reflect.get(...arguments);
|
|
87
|
+
if (prop.startsWith('__')) return Reflect.get(...arguments);
|
|
88
|
+
if (prop === 'callMethodIgnoreResult') return Reflect.get(...arguments);
|
|
89
|
+
if (prop === 'callMethod') return Reflect.get(...arguments);
|
|
90
|
+
|
|
91
|
+
return link.getProp(id, prop);
|
|
92
|
+
},
|
|
93
|
+
set: function (obj, prop, value) {
|
|
94
|
+
link.setProp(id, prop, value);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
apply: function (obj, thisArg, args) {
|
|
98
|
+
return link.call(id, args, parent_id);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
return new Proxy(target, handler);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class CrossLink {
|
|
105
|
+
constructor(connection) {
|
|
106
|
+
this.requestCounter = 1;
|
|
107
|
+
this.requests = {};
|
|
108
|
+
|
|
109
|
+
this.counter = 1;
|
|
110
|
+
this.objects = {};
|
|
111
|
+
|
|
112
|
+
this.connection = connection;
|
|
113
|
+
this.connection.onMessage((data) => this.onMessage(data));
|
|
114
|
+
this.connected = new Promise((resolve) => {
|
|
115
|
+
this.connection.onOpen(() => {
|
|
116
|
+
console.log('connection open');
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async _sendRequestAwaitResponse(data) {
|
|
123
|
+
//console.log('sending request', data);
|
|
124
|
+
const request_id = this.requestCounter++;
|
|
125
|
+
data.request_id = request_id;
|
|
126
|
+
try {
|
|
127
|
+
const result = await new Promise((resolve, reject) => {
|
|
128
|
+
this.requests[request_id] = resolve;
|
|
129
|
+
this.connection.send(JSON.stringify(data));
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
reject(
|
|
132
|
+
new Error(
|
|
133
|
+
`Timeout, request ${request_id}, data: ${JSON.stringify(data)}`
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
}, 10000);
|
|
137
|
+
});
|
|
138
|
+
// const t = Date.now() - requestData.sent;
|
|
139
|
+
return result;
|
|
140
|
+
} finally {
|
|
141
|
+
delete this.requests[request_id];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getProp(id, prop) {
|
|
146
|
+
return await this._sendRequestAwaitResponse({ type: 'get', id, prop });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getItem(id, key) {
|
|
150
|
+
return await this._sendRequestAwaitResponse({ type: 'get', id, key });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async setProp(id, prop, value) {
|
|
154
|
+
this.connection.send({
|
|
155
|
+
type: 'set',
|
|
156
|
+
id,
|
|
157
|
+
prop,
|
|
158
|
+
value: await this._dumpData(value),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async setItem(id, key, value) {
|
|
163
|
+
this.connection.send({
|
|
164
|
+
type: 'set',
|
|
165
|
+
id,
|
|
166
|
+
key,
|
|
167
|
+
value: await this._dumpData(value),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async call(id, args = [], parent_id = undefined, prop = undefined) {
|
|
172
|
+
return await this._sendRequestAwaitResponse({
|
|
173
|
+
type: 'call',
|
|
174
|
+
id,
|
|
175
|
+
parent_id,
|
|
176
|
+
args: await this._dumpData(args),
|
|
177
|
+
prop,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async callMethod(id, prop, args = []) {
|
|
182
|
+
return await this._sendRequestAwaitResponse({
|
|
183
|
+
type: 'call',
|
|
184
|
+
id,
|
|
185
|
+
args: await this._dumpData(args),
|
|
186
|
+
prop,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async callMethodIgnoreResult(id, prop, args = []) {
|
|
191
|
+
return await this.connection.send(
|
|
192
|
+
JSON.stringify({
|
|
193
|
+
type: 'call',
|
|
194
|
+
id,
|
|
195
|
+
args: await this._dumpData(args),
|
|
196
|
+
prop,
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
expose(name, obj) {
|
|
202
|
+
this.objects[name] = obj;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async _dumpData(data) {
|
|
206
|
+
//console.log("dumping data", data);
|
|
207
|
+
if (data === null) return undefined;
|
|
208
|
+
|
|
209
|
+
if (isPrimitive(data)) return data;
|
|
210
|
+
|
|
211
|
+
if (data instanceof MouseEvent) return serializeEvent(data);
|
|
212
|
+
if (data instanceof Event) return serializeEvent(data);
|
|
213
|
+
if (data instanceof InputEvent) return serializeEvent(data);
|
|
214
|
+
|
|
215
|
+
if (data instanceof ArrayBuffer)
|
|
216
|
+
return {
|
|
217
|
+
__is_crosslink_type__: true,
|
|
218
|
+
type: 'bytes',
|
|
219
|
+
value: encodeB64(data),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (data.constructor === Array) {
|
|
223
|
+
const result = [];
|
|
224
|
+
for (let item of data) result.push(await this._dumpData(item));
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (data.__is_crosslink_type__) return data;
|
|
229
|
+
|
|
230
|
+
if (isPrimitiveDict(data)) {
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
233
|
+
if (data.constructor === Object) {
|
|
234
|
+
const result = {};
|
|
235
|
+
Object.keys(data).map(async (key) => {
|
|
236
|
+
result[key] = await this._dumpData(data[key]);
|
|
237
|
+
});
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// complex type - store it in objects only send its id
|
|
242
|
+
const id = this.counter++;
|
|
243
|
+
this.objects[id] = data;
|
|
244
|
+
return {
|
|
245
|
+
__is_crosslink_type__: true,
|
|
246
|
+
type: 'proxy',
|
|
247
|
+
id,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
_loadValue(value) {
|
|
252
|
+
if (value === null || value === undefined) return undefined;
|
|
253
|
+
if (!value.__is_crosslink_type__) return value;
|
|
254
|
+
|
|
255
|
+
if (value.type == 'bytes') return decodeB64(value.value);
|
|
256
|
+
if (value.type == 'object') return this.objects[value.id];
|
|
257
|
+
if (value.type == 'proxy')
|
|
258
|
+
return createProxy(this, value.id, value.parent_id);
|
|
259
|
+
|
|
260
|
+
console.error('Cannot load value, unknown value type:', value);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
_loadData(data) {
|
|
264
|
+
if (
|
|
265
|
+
data === undefined ||
|
|
266
|
+
data === null ||
|
|
267
|
+
typeof data !== 'object' ||
|
|
268
|
+
data.__is_crosslink_type__
|
|
269
|
+
)
|
|
270
|
+
return this._loadValue(data);
|
|
271
|
+
|
|
272
|
+
Object.keys(data).map((key) => {
|
|
273
|
+
data[key] = this._loadData(data[key]);
|
|
274
|
+
});
|
|
275
|
+
return data;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async sendResponse(data, request_id, parent_id) {
|
|
279
|
+
if (request_id === undefined) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const value = await this._dumpData(await Promise.resolve(data));
|
|
284
|
+
//console.log('encoded data', value);
|
|
285
|
+
|
|
286
|
+
this.connection.send(
|
|
287
|
+
JSON.stringify({
|
|
288
|
+
type: 'response',
|
|
289
|
+
request_id,
|
|
290
|
+
value,
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async onMessage(event) {
|
|
296
|
+
const data = JSON.parse(event.data);
|
|
297
|
+
const request_id = data.request_id;
|
|
298
|
+
|
|
299
|
+
let obj = data.id ? this.objects[data.id] : window;
|
|
300
|
+
// console.log('onMessage', data, obj);
|
|
301
|
+
|
|
302
|
+
let response = null;
|
|
303
|
+
|
|
304
|
+
switch (data.type) {
|
|
305
|
+
case 'call':
|
|
306
|
+
const args = this._loadData(data.args);
|
|
307
|
+
let self = null;
|
|
308
|
+
if (data.prop) {
|
|
309
|
+
self = this.objects[data.id];
|
|
310
|
+
obj = self[data.prop];
|
|
311
|
+
} else {
|
|
312
|
+
self = this.objects[data.parent_id];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// console.log('calling', 'data', data, 'obj', obj, 'self', self, 'args', args);
|
|
316
|
+
|
|
317
|
+
response = obj.apply(self, args);
|
|
318
|
+
break;
|
|
319
|
+
case 'get_keys':
|
|
320
|
+
response = Object.keys(obj);
|
|
321
|
+
break;
|
|
322
|
+
case 'get':
|
|
323
|
+
if (data.prop) response = obj[data.prop];
|
|
324
|
+
else if (data.key) response = obj[data.key];
|
|
325
|
+
else response = obj;
|
|
326
|
+
if (response && (data.prop || data.key)) {
|
|
327
|
+
response = await this._dumpData(response);
|
|
328
|
+
if (typeof response === 'object') response.parent_id = data.id;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case 'set':
|
|
332
|
+
const value = this._loadData(data.value);
|
|
333
|
+
if (data.prop) obj[data.prop] = value;
|
|
334
|
+
if (data.key) obj[data.key] = value;
|
|
335
|
+
break;
|
|
336
|
+
case 'delete':
|
|
337
|
+
this.objects[data.id] = undefined;
|
|
338
|
+
break;
|
|
339
|
+
case 'response':
|
|
340
|
+
this.requests[request_id](this._loadData(data.value));
|
|
341
|
+
return;
|
|
342
|
+
default:
|
|
343
|
+
console.error('Unknown message type:', data, data.type);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (request_id !== undefined && data.type !== 'response') {
|
|
347
|
+
response = await response;
|
|
348
|
+
this.sendResponse(response, request_id);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function WebsocketLink(url) {
|
|
354
|
+
const socket = new WebSocket(url);
|
|
355
|
+
return new CrossLink({
|
|
356
|
+
send: (data) => socket.send(data),
|
|
357
|
+
onMessage: (callback) => (socket.onmessage = callback),
|
|
358
|
+
onOpen: (callback) => (socket.onopen = callback),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function WebworkerLink(worker) {
|
|
363
|
+
// wait for first message, which means the worker has loaded and is ready
|
|
364
|
+
const workerReady = new Promise((resolve) => {
|
|
365
|
+
worker.addEventListener(
|
|
366
|
+
'message',
|
|
367
|
+
(event) => {
|
|
368
|
+
resolve();
|
|
369
|
+
},
|
|
370
|
+
{ once: true }
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
return new CrossLink({
|
|
374
|
+
send: (data) => worker.postMessage(data),
|
|
375
|
+
onMessage: async (callback) => {
|
|
376
|
+
await workerReady;
|
|
377
|
+
worker.addEventListener('message', callback);
|
|
378
|
+
},
|
|
379
|
+
onOpen: async (callback) => {
|
|
380
|
+
await workerReady;
|
|
381
|
+
callback();
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
window.createLilGUI = async (args) => {
|
|
387
|
+
if (window.lil === undefined) {
|
|
388
|
+
const url = 'https://cdn.jsdelivr.net/npm/lil-gui@0.20';
|
|
389
|
+
if (window.define === undefined) {
|
|
390
|
+
await import(url);
|
|
391
|
+
} else {
|
|
392
|
+
await new Promise(async (resolve) => {
|
|
393
|
+
require([url], (module) => {
|
|
394
|
+
window.lil = module;
|
|
395
|
+
resolve();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return new window.lil.GUI(args);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
window.importPackage=async (url) => {
|
|
404
|
+
if (window.define === undefined) {
|
|
405
|
+
await import(url);
|
|
406
|
+
} else {
|
|
407
|
+
await new Promise(async (resolve) => {
|
|
408
|
+
require([url], (module) => {
|
|
409
|
+
resolve(module);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
window.patchedRequestAnimationFrame = (device, context, target) => {
|
|
416
|
+
// context.getCurrentTexture() is only guaranteed to be valid during the requestAnimationFrame callback
|
|
417
|
+
// Thus, in order to render from python asynchroniously, we are always rendering into a separate texture
|
|
418
|
+
// The actual callback here only copies the rendered image from the separate render target texture to the current texture
|
|
419
|
+
requestAnimationFrame((t) => {
|
|
420
|
+
const current = context.getCurrentTexture();
|
|
421
|
+
const encoder = device.createCommandEncoder();
|
|
422
|
+
|
|
423
|
+
encoder.copyTextureToTexture(
|
|
424
|
+
{ texture: target },
|
|
425
|
+
{ texture: current },
|
|
426
|
+
{ width: current.width, height: current.height }
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
device.queue.submit([encoder.finish()]);
|
|
430
|
+
});
|
|
431
|
+
};
|
webgpu/link/proxy.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from .base import LinkBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _ProxyIterator:
|
|
5
|
+
def __init__(self, proxy):
|
|
6
|
+
self._proxy = proxy
|
|
7
|
+
self._keys = proxy._get_keys()
|
|
8
|
+
self._index = 0
|
|
9
|
+
|
|
10
|
+
def __next__(self):
|
|
11
|
+
if self._index < len(self._keys):
|
|
12
|
+
key = self._keys[self._index]
|
|
13
|
+
self._index += 1
|
|
14
|
+
return key
|
|
15
|
+
else:
|
|
16
|
+
raise StopIteration
|
|
17
|
+
|
|
18
|
+
def __iter__(self):
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Proxy:
|
|
23
|
+
_link: LinkBase
|
|
24
|
+
_parent_id: int | None
|
|
25
|
+
_id: int | None
|
|
26
|
+
|
|
27
|
+
def __init__(self, link, parent_id=None, id=None):
|
|
28
|
+
self._link = link
|
|
29
|
+
self._id = id
|
|
30
|
+
self._parent_id = parent_id
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, key):
|
|
33
|
+
return self.__getattr__(key)
|
|
34
|
+
|
|
35
|
+
def __getattr__(self, key):
|
|
36
|
+
if (
|
|
37
|
+
key
|
|
38
|
+
in [
|
|
39
|
+
"_id",
|
|
40
|
+
"_parent_id",
|
|
41
|
+
"_link",
|
|
42
|
+
"_call",
|
|
43
|
+
"_call_method_ignore_return",
|
|
44
|
+
"_call_method",
|
|
45
|
+
]
|
|
46
|
+
or isinstance(key, str)
|
|
47
|
+
and key.startswith("__")
|
|
48
|
+
):
|
|
49
|
+
return super().__getattr__(key)
|
|
50
|
+
return self._link.get(self._id, key)
|
|
51
|
+
|
|
52
|
+
def __setattr__(self, key, value):
|
|
53
|
+
if key in ["_id", "_parent_id", "_link"]:
|
|
54
|
+
return super().__setattr__(key, value)
|
|
55
|
+
|
|
56
|
+
return self._link.set(self._id, key, value)
|
|
57
|
+
|
|
58
|
+
def __call__(self, *args, _ignore_result=False):
|
|
59
|
+
return self._link.call(self._id, list(args), self._parent_id, _ignore_result)
|
|
60
|
+
|
|
61
|
+
def _call_method(self, prop, args=[], ignore_result=False):
|
|
62
|
+
return self._link.call_method(self._id, prop, args, ignore_result)
|
|
63
|
+
|
|
64
|
+
def _call_method_ignore_return(self, prop, args=[]):
|
|
65
|
+
return self._link.call_method_ignore_return(self._id, prop, args)
|
|
66
|
+
|
|
67
|
+
def _new(self, *args):
|
|
68
|
+
return self._link.call_new(self._id, args=list(args))
|
|
69
|
+
|
|
70
|
+
def _to_js(self):
|
|
71
|
+
return {
|
|
72
|
+
"type": "proxy",
|
|
73
|
+
"id": self._id,
|
|
74
|
+
"parent_id": self._parent_id,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def _get_keys(self):
|
|
78
|
+
return self._link.get_keys(self._id)
|
|
79
|
+
|
|
80
|
+
def __iter__(self):
|
|
81
|
+
return _ProxyIterator(self)
|
webgpu/link/websocket.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import websockets
|
|
6
|
+
import websockets.asyncio.client
|
|
7
|
+
|
|
8
|
+
from .base import LinkBaseAsync
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WebsocketLinkBase(LinkBaseAsync):
|
|
12
|
+
_websocket_thread: threading.Thread
|
|
13
|
+
_connection: websockets.asyncio.client.ClientConnection
|
|
14
|
+
_event_is_connected: threading.Event
|
|
15
|
+
_start_handling_messages: threading.Event
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self._event_is_connected = threading.Event()
|
|
20
|
+
self._start_handling_messages = threading.Event()
|
|
21
|
+
self._send_loop = asyncio.new_event_loop()
|
|
22
|
+
|
|
23
|
+
self._websocket_thread = threading.Thread(target=self._connect)
|
|
24
|
+
self._websocket_thread.start()
|
|
25
|
+
|
|
26
|
+
def wait_for_connection(self):
|
|
27
|
+
self._event_is_connected.wait()
|
|
28
|
+
|
|
29
|
+
async def _send_async(self, message):
|
|
30
|
+
if self._connection:
|
|
31
|
+
await self._connection.send(message)
|
|
32
|
+
else:
|
|
33
|
+
raise Exception("Websocket not connected")
|
|
34
|
+
|
|
35
|
+
def _connect(self):
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class WebsocketLinkClient(WebsocketLinkBase):
|
|
40
|
+
_url: str
|
|
41
|
+
|
|
42
|
+
def __init__(self, url):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self._url = url
|
|
45
|
+
|
|
46
|
+
def _connect(self):
|
|
47
|
+
async def start_websocket():
|
|
48
|
+
async for websocket in websockets.connect(self._url):
|
|
49
|
+
try:
|
|
50
|
+
# print("client connected")
|
|
51
|
+
self._connection = websocket
|
|
52
|
+
self._event_is_connected.set()
|
|
53
|
+
self._start_handling_messages.wait()
|
|
54
|
+
async for message in websocket:
|
|
55
|
+
self._on_message(message)
|
|
56
|
+
except websockets.exceptions.ConnectionClosed:
|
|
57
|
+
continue
|
|
58
|
+
except Exception:
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
asyncio.set_event_loop(self._send_loop)
|
|
63
|
+
self._send_loop.run_until_complete(start_websocket())
|
|
64
|
+
except Exception:
|
|
65
|
+
print("closing connection")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class WebsocketLinkServer(WebsocketLinkBase):
|
|
69
|
+
_stop: asyncio.Future
|
|
70
|
+
_port: int = 0
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
super().__init__()
|
|
74
|
+
self._stop = self._send_loop.create_future()
|
|
75
|
+
self._port = 8700
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def port(self):
|
|
79
|
+
return self._port
|
|
80
|
+
|
|
81
|
+
async def _websocket_handler(self, websocket, path=""):
|
|
82
|
+
try:
|
|
83
|
+
# print("client connected")
|
|
84
|
+
self._connection = websocket
|
|
85
|
+
self._event_is_connected.set()
|
|
86
|
+
async for message in websocket:
|
|
87
|
+
thread = threading.Thread(target=self._on_message, args=(message,))
|
|
88
|
+
thread.start()
|
|
89
|
+
finally:
|
|
90
|
+
self._connection = None
|
|
91
|
+
|
|
92
|
+
def _connect(self):
|
|
93
|
+
async def start_websocket():
|
|
94
|
+
while True:
|
|
95
|
+
try:
|
|
96
|
+
async with websockets.serve(
|
|
97
|
+
self._websocket_handler, "", self._port, max_queue=128
|
|
98
|
+
):
|
|
99
|
+
# print("server running on port", self._port)
|
|
100
|
+
await self._stop
|
|
101
|
+
break
|
|
102
|
+
except OSError as e:
|
|
103
|
+
self._port += 1
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print("error in websocket server", e)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
asyncio.set_event_loop(self._send_loop)
|
|
109
|
+
self._send_loop.run_until_complete(start_websocket())
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print("exception in _start_websocket_server", e)
|
|
112
|
+
print("stopped websocket")
|
|
113
|
+
|
|
114
|
+
def stop(self):
|
|
115
|
+
self._send_loop.call_soon_threadsafe(self._stop.set_result, None)
|