@perspective-dev/client 4.0.0

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.
Files changed (91) hide show
  1. package/LICENSE.md +193 -0
  2. package/README.md +3 -0
  3. package/dist/cdn/perspective-server.worker.js +2 -0
  4. package/dist/cdn/perspective-server.worker.js.map +7 -0
  5. package/dist/cdn/perspective.js +3 -0
  6. package/dist/cdn/perspective.js.map +7 -0
  7. package/dist/esm/perspective-server.worker.d.ts +1 -0
  8. package/dist/esm/perspective.browser.d.ts +14 -0
  9. package/dist/esm/perspective.inline.js +3 -0
  10. package/dist/esm/perspective.inline.js.map +7 -0
  11. package/dist/esm/perspective.js +3 -0
  12. package/dist/esm/perspective.js.map +7 -0
  13. package/dist/esm/perspective.node.d.ts +60 -0
  14. package/dist/esm/perspective.node.js +2431 -0
  15. package/dist/esm/perspective.node.js.map +7 -0
  16. package/dist/esm/ts-rs/Aggregate.d.ts +1 -0
  17. package/dist/esm/ts-rs/ColumnWindow.d.ts +4 -0
  18. package/dist/esm/ts-rs/DeleteOptions.d.ts +6 -0
  19. package/dist/esm/ts-rs/Expressions.d.ts +3 -0
  20. package/dist/esm/ts-rs/Filter.d.ts +2 -0
  21. package/dist/esm/ts-rs/FilterReducer.d.ts +1 -0
  22. package/dist/esm/ts-rs/FilterTerm.d.ts +2 -0
  23. package/dist/esm/ts-rs/OnUpdateMode.d.ts +9 -0
  24. package/dist/esm/ts-rs/OnUpdateOptions.d.ts +7 -0
  25. package/dist/esm/ts-rs/Scalar.d.ts +5 -0
  26. package/dist/esm/ts-rs/Sort.d.ts +2 -0
  27. package/dist/esm/ts-rs/SortDir.d.ts +1 -0
  28. package/dist/esm/ts-rs/SystemInfo.d.ts +40 -0
  29. package/dist/esm/ts-rs/TableInitOptions.d.ts +22 -0
  30. package/dist/esm/ts-rs/TableReadFormat.d.ts +7 -0
  31. package/dist/esm/ts-rs/UpdateOptions.d.ts +8 -0
  32. package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +90 -0
  33. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
  34. package/dist/esm/ts-rs/ViewWindow.d.ts +23 -0
  35. package/dist/esm/wasm/browser.d.ts +21 -0
  36. package/dist/esm/wasm/decompress.d.ts +1 -0
  37. package/dist/esm/wasm/emscripten_api.d.ts +5 -0
  38. package/dist/esm/wasm/engine.d.ts +40 -0
  39. package/dist/esm/wasm/perspective-server.poly.d.ts +1 -0
  40. package/dist/esm/websocket.d.ts +4 -0
  41. package/dist/wasm/perspective-js.d.ts +712 -0
  42. package/dist/wasm/perspective-js.js +1934 -0
  43. package/dist/wasm/perspective-js.wasm +0 -0
  44. package/dist/wasm/perspective-js.wasm.d.ts +75 -0
  45. package/package.json +68 -0
  46. package/src/rust/client.rs +483 -0
  47. package/src/rust/lib.rs +70 -0
  48. package/src/rust/table.rs +364 -0
  49. package/src/rust/table_data.rs +159 -0
  50. package/src/rust/utils/browser.rs +39 -0
  51. package/src/rust/utils/console_logger.rs +236 -0
  52. package/src/rust/utils/errors.rs +288 -0
  53. package/src/rust/utils/futures.rs +174 -0
  54. package/src/rust/utils/json.rs +252 -0
  55. package/src/rust/utils/local_poll_loop.rs +63 -0
  56. package/src/rust/utils/mod.rs +32 -0
  57. package/src/rust/utils/serde.rs +46 -0
  58. package/src/rust/utils/trace_allocator.rs +98 -0
  59. package/src/rust/view.rs +355 -0
  60. package/src/ts/perspective-server.worker.ts +54 -0
  61. package/src/ts/perspective.browser.ts +132 -0
  62. package/src/ts/perspective.cdn.ts +22 -0
  63. package/src/ts/perspective.inline.ts +27 -0
  64. package/src/ts/perspective.node.ts +315 -0
  65. package/src/ts/ts-rs/Aggregate.ts +3 -0
  66. package/src/ts/ts-rs/ColumnWindow.ts +3 -0
  67. package/src/ts/ts-rs/DeleteOptions.ts +6 -0
  68. package/src/ts/ts-rs/Expressions.ts +3 -0
  69. package/src/ts/ts-rs/Filter.ts +4 -0
  70. package/src/ts/ts-rs/FilterReducer.ts +3 -0
  71. package/src/ts/ts-rs/FilterTerm.ts +4 -0
  72. package/src/ts/ts-rs/OnUpdateData.ts +8 -0
  73. package/src/ts/ts-rs/OnUpdateMode.ts +11 -0
  74. package/src/ts/ts-rs/OnUpdateOptions.ts +7 -0
  75. package/src/ts/ts-rs/Scalar.ts +7 -0
  76. package/src/ts/ts-rs/Sort.ts +4 -0
  77. package/src/ts/ts-rs/SortDir.ts +3 -0
  78. package/src/ts/ts-rs/SystemInfo.ts +41 -0
  79. package/src/ts/ts-rs/TableInitOptions.ts +21 -0
  80. package/src/ts/ts-rs/TableReadFormat.ts +9 -0
  81. package/src/ts/ts-rs/UpdateOptions.ts +7 -0
  82. package/src/ts/ts-rs/ViewConfigUpdate.ts +87 -0
  83. package/src/ts/ts-rs/ViewOnUpdateResp.ts +3 -0
  84. package/src/ts/ts-rs/ViewWindow.ts +17 -0
  85. package/src/ts/wasm/browser.ts +123 -0
  86. package/src/ts/wasm/decompress.ts +64 -0
  87. package/src/ts/wasm/emscripten_api.ts +63 -0
  88. package/src/ts/wasm/engine.ts +271 -0
  89. package/src/ts/wasm/perspective-server.poly.ts +244 -0
  90. package/src/ts/websocket.ts +95 -0
  91. package/tsconfig.json +20 -0
@@ -0,0 +1,123 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import type * as psp from "../../../dist/wasm/perspective-js.d.ts";
14
+
15
+ import * as psp_websocket from "../websocket.ts";
16
+
17
+ function invert_promise<T>(): [
18
+ (t: T) => void,
19
+ Promise<T>,
20
+ (e: string) => void,
21
+ ] {
22
+ let sender, reject;
23
+ let receiver: Promise<T> = new Promise((x, y) => {
24
+ sender = x;
25
+ reject = y;
26
+ });
27
+
28
+ return [
29
+ sender as unknown as (t: T) => void,
30
+ receiver,
31
+ reject as unknown as (e: string) => void,
32
+ ];
33
+ }
34
+
35
+ async function _init(ws: MessagePort | Worker, wasm: WebAssembly.Module) {
36
+ const [sender, receiver] = invert_promise();
37
+ ws.addEventListener("message", function listener(resp) {
38
+ ws.removeEventListener("message", listener);
39
+ sender(null);
40
+ });
41
+
42
+ ws.onmessage = function listener(resp) {
43
+ ws.onmessage = function () {};
44
+ sender(null);
45
+ };
46
+
47
+ ws.onmessageerror = console.error;
48
+ ws.postMessage(
49
+ { cmd: "init", args: [wasm] },
50
+ { transfer: wasm instanceof WebAssembly.Module ? [] : [wasm] },
51
+ );
52
+
53
+ await receiver;
54
+ }
55
+
56
+ /**
57
+ * Create a new client connected exclusively to a new Web Worker instance of
58
+ * the Perspective engine.
59
+ * @param module
60
+ * @returns
61
+ */
62
+ export async function worker(
63
+ module: Promise<typeof psp>,
64
+ server_wasm: Promise<WebAssembly.Module>,
65
+ perspective_wasm_worker: Promise<SharedWorker | ServiceWorker | Worker>,
66
+ ) {
67
+ let [wasm, webworker]: [
68
+ WebAssembly.Module,
69
+ SharedWorker | ServiceWorker | Worker | MessagePort,
70
+ ] = await Promise.all([server_wasm, perspective_wasm_worker]);
71
+
72
+ const { Client } = await module;
73
+ let port: MessagePort;
74
+ if (
75
+ typeof SharedWorker !== "undefined" &&
76
+ webworker instanceof SharedWorker
77
+ ) {
78
+ port = webworker.port;
79
+ } else {
80
+ webworker = webworker as ServiceWorker | Worker | MessagePort;
81
+ const messageChannel = new MessageChannel();
82
+ webworker.postMessage(null, [messageChannel.port2]);
83
+ port = messageChannel.port1;
84
+ }
85
+
86
+ const client = new Client(
87
+ async (proto: Uint8Array) => {
88
+ const f = proto.slice().buffer;
89
+ port.postMessage(f, { transfer: [f] });
90
+ },
91
+ async () => {
92
+ console.debug("Closing WebWorker");
93
+ port.close();
94
+ if (webworker instanceof Worker) {
95
+ webworker.terminate();
96
+ }
97
+ },
98
+ );
99
+
100
+ await _init(port, wasm);
101
+ port.addEventListener("message", (json: MessageEvent<Uint8Array>) => {
102
+ client.handle_response(json.data);
103
+ });
104
+
105
+ return client;
106
+ }
107
+
108
+ /**
109
+ * Create a new client connected via WebSocket to a server implemnting the
110
+ * Perspective Protocol.
111
+ * @param module
112
+ * @param url
113
+ * @returns
114
+ */
115
+ export async function websocket(
116
+ module: Promise<typeof psp>,
117
+ url: string | URL,
118
+ ) {
119
+ const { Client } = await module;
120
+ return await psp_websocket.websocket(WebSocket, Client, url);
121
+ }
122
+
123
+ export default { websocket, worker };
@@ -0,0 +1,64 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ // @ts-ignore
14
+ import { extract } from "pro_self_extracting_wasm";
15
+
16
+ if (typeof WebAssembly === "undefined") {
17
+ throw new Error("WebAssembly not supported.");
18
+ }
19
+
20
+ interface Module extends WebAssembly.Exports {
21
+ size(): number;
22
+ offset(): number;
23
+ memory: WebAssembly.Memory;
24
+ }
25
+
26
+ // Perform a silly dance to deal with the different ways webpack and esbuild
27
+ // load binary, as this may either be an `ArrayBuffer` or `URL` depending
28
+ // on whether `inline` option was specified to `perspective-esbuild-plugin`.
29
+ async function compile(
30
+ buffer: ArrayBuffer | Response | WebAssembly.Module | WebAssembly.Exports,
31
+ ): Promise<Module> {
32
+ if (buffer instanceof Response) {
33
+ return (await WebAssembly.instantiateStreaming(buffer)).instance
34
+ .exports as Module;
35
+ } else if (buffer instanceof WebAssembly.Module) {
36
+ return (await WebAssembly.instantiate(buffer)).exports as Module;
37
+ } else if (buffer instanceof WebAssembly.Instance) {
38
+ return buffer.exports as Module;
39
+ } else if (buffer instanceof ArrayBuffer) {
40
+ return (await WebAssembly.instantiate(buffer as BufferSource)).instance
41
+ .exports as Module;
42
+ } else {
43
+ return buffer as Module;
44
+ }
45
+ }
46
+
47
+ export async function load_wasm_stage_0(
48
+ wasm:
49
+ | ArrayBuffer
50
+ | Response
51
+ | WebAssembly.Module
52
+ | (() => Promise<ArrayBuffer>),
53
+ ): Promise<Uint8Array> {
54
+ if (wasm instanceof Function) {
55
+ wasm = await wasm();
56
+ }
57
+
58
+ try {
59
+ return await extract(wasm as ArrayBuffer);
60
+ } catch (e) {
61
+ console.warn("Stage 0 wasm loading failed, skipping");
62
+ return new Uint8Array(wasm as ArrayBuffer);
63
+ }
64
+ }
@@ -0,0 +1,63 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import * as perspective_server from "./perspective-server.poly.ts";
14
+ export type * from "../../../dist/wasm/perspective-js.js";
15
+ import type * as perspective_server_t from "@perspective-dev/server/dist/wasm/perspective-server.js";
16
+
17
+ export type PspPtr = BigInt | number;
18
+ export type EmscriptenServer = bigint | number;
19
+
20
+ export async function compile_perspective(
21
+ wasmBinary: ArrayBuffer,
22
+ ): Promise<perspective_server_t.MainModule> {
23
+ const module = await perspective_server.default({
24
+ locateFile(x: any) {
25
+ return x;
26
+ },
27
+ instantiateWasm: async (
28
+ imports: any,
29
+ receive: (_: WebAssembly.Instance) => void,
30
+ ) => {
31
+ imports["env"] = {
32
+ ...imports["env"],
33
+ psp_stack_trace() {
34
+ const str = Error().stack || "";
35
+ const textEncoder = new TextEncoder();
36
+ const bytes = textEncoder.encode(str);
37
+ const ptr = module._psp_alloc(
38
+ module._psp_is_memory64()
39
+ ? (BigInt(bytes.byteLength + 1) as any as number)
40
+ : bytes.byteLength + 1,
41
+ );
42
+
43
+ module.HEAPU8.set(bytes, Number(ptr));
44
+ module.HEAPU8[Number(ptr) + bytes.byteLength] = 0;
45
+ return ptr;
46
+ },
47
+ psp_heap_size() {
48
+ if (module._psp_is_memory64()) {
49
+ return BigInt(module.HEAPU8.buffer.byteLength);
50
+ } else {
51
+ return module.HEAPU8.buffer.byteLength;
52
+ }
53
+ },
54
+ };
55
+
56
+ const webasm = await WebAssembly.instantiate(wasmBinary, imports);
57
+ receive(webasm.instance);
58
+ return webasm.instance.exports;
59
+ },
60
+ });
61
+
62
+ return module;
63
+ }
@@ -0,0 +1,271 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import type { MainModule } from "@perspective-dev/server/dist/wasm/perspective-server.js";
14
+ import type { EmscriptenServer, PspPtr } from "./emscripten_api.ts";
15
+
16
+ export type ApiResponse = {
17
+ client_id: number;
18
+ data: Uint8Array;
19
+ };
20
+
21
+ export interface PerspectiveServerOptions {
22
+ on_poll_request?: (x: PerspectiveServer) => Promise<void>;
23
+ }
24
+
25
+ export class PerspectivePollThread {
26
+ private poll_handle?: Promise<void>;
27
+ private server: PerspectiveServer;
28
+ constructor(server: PerspectiveServer) {
29
+ this.server = server;
30
+ }
31
+
32
+ private set_poll_handle() {
33
+ this.poll_handle = new Promise((resolve, reject) =>
34
+ setTimeout(() =>
35
+ this.server
36
+ .poll()
37
+ .then(resolve)
38
+ .catch(reject)
39
+ .finally(() => {
40
+ this.poll_handle = undefined;
41
+ }),
42
+ ),
43
+ );
44
+
45
+ return this.poll_handle;
46
+ }
47
+
48
+ async on_poll_request() {
49
+ if (!this.poll_handle) {
50
+ await this.set_poll_handle();
51
+ } else {
52
+ await this.poll_handle.then(() => {
53
+ if (!this.poll_handle) {
54
+ return this.set_poll_handle();
55
+ }
56
+ });
57
+ }
58
+ }
59
+ }
60
+
61
+ export class PerspectiveServer {
62
+ clients: Map<number, (buffer: Uint8Array) => Promise<void>>;
63
+ server: EmscriptenServer;
64
+ module: MainModule;
65
+ on_poll_request?: (x: PerspectiveServer) => Promise<void>;
66
+ constructor(module: MainModule, options?: PerspectiveServerOptions) {
67
+ this.clients = new Map();
68
+ this.module = module;
69
+ this.on_poll_request = options?.on_poll_request;
70
+ this.server = module._psp_new_server(
71
+ !!options?.on_poll_request ? 1 : 0,
72
+ ) as EmscriptenServer;
73
+ }
74
+
75
+ /**
76
+ * Helper function to create server emitter/receiver pairs
77
+ */
78
+ make_session(
79
+ callback: (buffer: Uint8Array) => Promise<void>,
80
+ ): PerspectiveSession {
81
+ const client_id = this.module._psp_new_session(this.server as any);
82
+ this.clients.set(client_id, callback);
83
+ return new PerspectiveSession(
84
+ this.module,
85
+ this.server,
86
+ client_id,
87
+ this.clients,
88
+ this.on_poll_request && (() => this.on_poll_request!(this)),
89
+ );
90
+ }
91
+
92
+ async poll() {
93
+ const polled = this.module._psp_poll(this.server as any);
94
+ await decode_api_responses(
95
+ this.module,
96
+ polled,
97
+ async (msg: ApiResponse) => {
98
+ await this.clients.get(msg.client_id)!(msg.data);
99
+ },
100
+ );
101
+ }
102
+
103
+ delete() {
104
+ this.module._psp_delete_server(this.server as any);
105
+ }
106
+ }
107
+
108
+ export class PerspectiveSession {
109
+ constructor(
110
+ private mod: MainModule,
111
+ private server: EmscriptenServer,
112
+ private client_id: number,
113
+ private client_map: Map<number, (buffer: Uint8Array) => Promise<void>>,
114
+ private on_poll_request?: () => Promise<void>,
115
+ ) {}
116
+
117
+ async handle_request(view: Uint8Array) {
118
+ const ptr = await convert_typed_array_to_pointer(
119
+ this.mod,
120
+ view,
121
+ async (viewPtr) => {
122
+ return this.mod._psp_handle_request(
123
+ this.server as any,
124
+ this.client_id,
125
+ viewPtr as any,
126
+ this.mod._psp_is_memory64()
127
+ ? (BigInt(view.byteLength) as any as number)
128
+ : (view.byteLength as any),
129
+ );
130
+ },
131
+ );
132
+
133
+ await decode_api_responses(this.mod, ptr, async (msg: ApiResponse) => {
134
+ await this.client_map.get(msg.client_id)!(msg.data);
135
+ });
136
+
137
+ if (this.on_poll_request) {
138
+ await this.on_poll_request();
139
+ } else {
140
+ await this.poll();
141
+ }
142
+ }
143
+
144
+ private async poll() {
145
+ const polled = this.mod._psp_poll(this.server as any);
146
+ await decode_api_responses(
147
+ this.mod,
148
+ polled,
149
+ async (msg: ApiResponse) => {
150
+ if (msg.client_id === 0) {
151
+ await this.client_map.get(this.client_id)!(msg.data);
152
+ } else {
153
+ await this.client_map.get(msg.client_id)!(msg.data);
154
+ }
155
+ },
156
+ );
157
+ }
158
+
159
+ close() {
160
+ this.mod._psp_close_session(this.server as any, this.client_id);
161
+ }
162
+ }
163
+
164
+ async function convert_typed_array_to_pointer(
165
+ core: MainModule,
166
+ array: Uint8Array,
167
+ callback: (_: PspPtr) => Promise<PspPtr>,
168
+ ): Promise<PspPtr> {
169
+ const ptr = core._psp_alloc(
170
+ core._psp_is_memory64()
171
+ ? (BigInt(array.byteLength) as any as number)
172
+ : (array.byteLength as any),
173
+ );
174
+
175
+ core.HEAPU8.set(array, Number(ptr));
176
+ const msg = await callback(ptr);
177
+ core._psp_free(ptr);
178
+ return msg;
179
+ }
180
+
181
+ /**
182
+ * Convert a pointer to WASM memory into an `ApiResponse[]`, via a custom
183
+ * encoding.
184
+ *
185
+ * @param core The emscripten API
186
+ * @param ptr A pointer to a fixed-sized struct representing a set of
187
+ * `proto::Resp` payloads, encoded as a length-prefixed array of
188
+ * (char* data, u32_t len, u32_t client_id) tuples:
189
+ *
190
+ * ```text
191
+ * N data length client_id data length client_id
192
+ * +-------------------------------------------------------------+
193
+ * | 2 | 0xabc | 9 | 0 | 0xdef | 12 | 0 |
194
+ * +-------------------------------------------------------------+
195
+ * | |
196
+ * | +-------------+ | +----------------+
197
+ * +--| "Test Data" | +--| "Hello, World" |
198
+ * +-------------+ +----------------+
199
+ * ```
200
+ *
201
+ * @param callback A callback to which is passed the responses. THe responses
202
+ * must be fully processed or copied before the callback returns, as it
203
+ * references memory on the wasm stack.
204
+ */
205
+ async function decode_api_responses(
206
+ core: MainModule,
207
+ ptr: PspPtr,
208
+ callback: (_: ApiResponse) => Promise<void>,
209
+ ) {
210
+ const is_64 = core._psp_is_memory64();
211
+ const response = new DataView(
212
+ core.HEAPU8.buffer,
213
+ Number(ptr),
214
+ is_64 ? 12 : 8,
215
+ );
216
+
217
+ const num_msgs = response.getUint32(0, true);
218
+ const msgs_ptr = is_64
219
+ ? response.getBigInt64(4, true)
220
+ : response.getUint32(4, true);
221
+
222
+ const messages = new DataView(
223
+ core.HEAPU8.buffer,
224
+ Number(msgs_ptr),
225
+ num_msgs * (is_64 ? 16 : 12),
226
+ );
227
+
228
+ try {
229
+ for (let i = 0; i < num_msgs; i++) {
230
+ const [data_ptr, data_len, client_id] = is_64
231
+ ? [
232
+ messages.getBigInt64(i * 16, true),
233
+ messages.getInt32(i * 16 + 8, true),
234
+ messages.getInt32(i * 16 + 12, true),
235
+ ]
236
+ : [
237
+ messages.getInt32(i * 12, true),
238
+ messages.getInt32(i * 12 + 4, true),
239
+ messages.getInt32(i * 12 + 8, true),
240
+ ];
241
+
242
+ const data = new Uint8Array(
243
+ core.HEAPU8.buffer,
244
+ Number(data_ptr),
245
+ data_len,
246
+ );
247
+
248
+ const resp = { client_id, data };
249
+ await callback(resp);
250
+ }
251
+ } finally {
252
+ for (let i = 0; i < num_msgs; i++) {
253
+ const data_ptr = is_64
254
+ ? messages.getBigInt64(i * 16, true)
255
+ : messages.getInt32(i * 12, true);
256
+
257
+ core._psp_free(data_ptr as any);
258
+ }
259
+
260
+ core._psp_free(
261
+ is_64
262
+ ? (BigInt(messages.byteOffset) as any as number)
263
+ : (messages.byteOffset as any),
264
+ );
265
+ core._psp_free(
266
+ is_64
267
+ ? (BigInt(response.byteOffset) as any as number)
268
+ : (response.byteOffset as any),
269
+ );
270
+ }
271
+ }