@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.
- package/LICENSE.md +193 -0
- package/README.md +3 -0
- package/dist/cdn/perspective-server.worker.js +2 -0
- package/dist/cdn/perspective-server.worker.js.map +7 -0
- package/dist/cdn/perspective.js +3 -0
- package/dist/cdn/perspective.js.map +7 -0
- package/dist/esm/perspective-server.worker.d.ts +1 -0
- package/dist/esm/perspective.browser.d.ts +14 -0
- package/dist/esm/perspective.inline.js +3 -0
- package/dist/esm/perspective.inline.js.map +7 -0
- package/dist/esm/perspective.js +3 -0
- package/dist/esm/perspective.js.map +7 -0
- package/dist/esm/perspective.node.d.ts +60 -0
- package/dist/esm/perspective.node.js +2431 -0
- package/dist/esm/perspective.node.js.map +7 -0
- package/dist/esm/ts-rs/Aggregate.d.ts +1 -0
- package/dist/esm/ts-rs/ColumnWindow.d.ts +4 -0
- package/dist/esm/ts-rs/DeleteOptions.d.ts +6 -0
- package/dist/esm/ts-rs/Expressions.d.ts +3 -0
- package/dist/esm/ts-rs/Filter.d.ts +2 -0
- package/dist/esm/ts-rs/FilterReducer.d.ts +1 -0
- package/dist/esm/ts-rs/FilterTerm.d.ts +2 -0
- package/dist/esm/ts-rs/OnUpdateMode.d.ts +9 -0
- package/dist/esm/ts-rs/OnUpdateOptions.d.ts +7 -0
- package/dist/esm/ts-rs/Scalar.d.ts +5 -0
- package/dist/esm/ts-rs/Sort.d.ts +2 -0
- package/dist/esm/ts-rs/SortDir.d.ts +1 -0
- package/dist/esm/ts-rs/SystemInfo.d.ts +40 -0
- package/dist/esm/ts-rs/TableInitOptions.d.ts +22 -0
- package/dist/esm/ts-rs/TableReadFormat.d.ts +7 -0
- package/dist/esm/ts-rs/UpdateOptions.d.ts +8 -0
- package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +90 -0
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
- package/dist/esm/ts-rs/ViewWindow.d.ts +23 -0
- package/dist/esm/wasm/browser.d.ts +21 -0
- package/dist/esm/wasm/decompress.d.ts +1 -0
- package/dist/esm/wasm/emscripten_api.d.ts +5 -0
- package/dist/esm/wasm/engine.d.ts +40 -0
- package/dist/esm/wasm/perspective-server.poly.d.ts +1 -0
- package/dist/esm/websocket.d.ts +4 -0
- package/dist/wasm/perspective-js.d.ts +712 -0
- package/dist/wasm/perspective-js.js +1934 -0
- package/dist/wasm/perspective-js.wasm +0 -0
- package/dist/wasm/perspective-js.wasm.d.ts +75 -0
- package/package.json +68 -0
- package/src/rust/client.rs +483 -0
- package/src/rust/lib.rs +70 -0
- package/src/rust/table.rs +364 -0
- package/src/rust/table_data.rs +159 -0
- package/src/rust/utils/browser.rs +39 -0
- package/src/rust/utils/console_logger.rs +236 -0
- package/src/rust/utils/errors.rs +288 -0
- package/src/rust/utils/futures.rs +174 -0
- package/src/rust/utils/json.rs +252 -0
- package/src/rust/utils/local_poll_loop.rs +63 -0
- package/src/rust/utils/mod.rs +32 -0
- package/src/rust/utils/serde.rs +46 -0
- package/src/rust/utils/trace_allocator.rs +98 -0
- package/src/rust/view.rs +355 -0
- package/src/ts/perspective-server.worker.ts +54 -0
- package/src/ts/perspective.browser.ts +132 -0
- package/src/ts/perspective.cdn.ts +22 -0
- package/src/ts/perspective.inline.ts +27 -0
- package/src/ts/perspective.node.ts +315 -0
- package/src/ts/ts-rs/Aggregate.ts +3 -0
- package/src/ts/ts-rs/ColumnWindow.ts +3 -0
- package/src/ts/ts-rs/DeleteOptions.ts +6 -0
- package/src/ts/ts-rs/Expressions.ts +3 -0
- package/src/ts/ts-rs/Filter.ts +4 -0
- package/src/ts/ts-rs/FilterReducer.ts +3 -0
- package/src/ts/ts-rs/FilterTerm.ts +4 -0
- package/src/ts/ts-rs/OnUpdateData.ts +8 -0
- package/src/ts/ts-rs/OnUpdateMode.ts +11 -0
- package/src/ts/ts-rs/OnUpdateOptions.ts +7 -0
- package/src/ts/ts-rs/Scalar.ts +7 -0
- package/src/ts/ts-rs/Sort.ts +4 -0
- package/src/ts/ts-rs/SortDir.ts +3 -0
- package/src/ts/ts-rs/SystemInfo.ts +41 -0
- package/src/ts/ts-rs/TableInitOptions.ts +21 -0
- package/src/ts/ts-rs/TableReadFormat.ts +9 -0
- package/src/ts/ts-rs/UpdateOptions.ts +7 -0
- package/src/ts/ts-rs/ViewConfigUpdate.ts +87 -0
- package/src/ts/ts-rs/ViewOnUpdateResp.ts +3 -0
- package/src/ts/ts-rs/ViewWindow.ts +17 -0
- package/src/ts/wasm/browser.ts +123 -0
- package/src/ts/wasm/decompress.ts +64 -0
- package/src/ts/wasm/emscripten_api.ts +63 -0
- package/src/ts/wasm/engine.ts +271 -0
- package/src/ts/wasm/perspective-server.poly.ts +244 -0
- package/src/ts/websocket.ts +95 -0
- 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
|
+
}
|