1use crate::utils::StrUtils;
4use base64::Engine;
5use std::io::{self, Write};
6use std::sync::LazyLock;
7
8const CHUNK_WORDS: usize = 8192;
9
10pub struct WasmJsWriter<W: Write> {
11 out: W,
12 imports_module: String,
13 wasm_buf: [u8; CHUNK_WORDS * 3],
14 out_buf: [u8; CHUNK_WORDS * 4],
15 n: usize,
16 started: bool,
17 finished: bool,
18}
19
20static PROLOG: LazyLock<Vec<u8>> = LazyLock::new(|| {
21 r#"
22const CHUNK_STACK = [
23""#
24 .to_os_bytes()
25});
26
27static CHUNK_SEP: LazyLock<Vec<u8>> = LazyLock::new(|| {
28 r#"",
29""#
30 .to_os_bytes()
31});
32
33static EPILOG: LazyLock<Vec<u8>> = LazyLock::new(|| {
34 r#""
35].reverse();
36
37async function chunkBytes(base64) {
38 if (typeof Buffer !== 'undefined') {
39 return Buffer.from(base64, 'base64');
40 }
41 const res = await fetch("data:application/octet-stream;base64," + base64);
42 return res.bytes();
43}
44
45export const WASM_PROMISE = (async () => {
46 const compressed = new ReadableStream({
47 type: 'bytes',
48 cancel: () => {
49 CHUNK_STACK.length = 0;
50 },
51 pull: async (ctrl) => {
52 if (CHUNK_STACK.length) {
53 ctrl.enqueue(await chunkBytes(CHUNK_STACK.pop()));
54 } else {
55 ctrl.close();
56 }
57 }
58 });
59 const body = compressed.pipeThrough(new DecompressionStream('deflate'));
60 const response = new Response(body,
61 {
62 status: 200,
63 statusText: 'OK',
64 headers: {
65 'content-type': 'application/wasm'
66 }
67 });
68 const {instance} = await WebAssembly.instantiateStreaming(response, {
69 [IMPORTS_KEY]: importObject
70 });
71 importObject.__wbg_set_wasm(instance.exports);
72 instance.exports.__wbindgen_start();
73 return importObject;
74})();
75
76export function getWasm() {
77 return WASM_PROMISE;
78}
79"#
80 .to_os_bytes()
81});
82
83impl<W: Write> WasmJsWriter<W> {
84 pub fn new(out: W, imports_module: &str) -> Self {
85 Self {
86 out,
87 imports_module: imports_module.to_string(),
88 wasm_buf: [0; CHUNK_WORDS * 3],
89 out_buf: [0; CHUNK_WORDS * 4],
90 n: 0,
91 started: false,
92 finished: false,
93 }
94 }
95
96 fn push_chunk(&mut self) -> std::io::Result<()> {
97 if self.finished {
98 return Err(io::Error::new(
99 std::io::ErrorKind::Other,
100 "Cannot write to finished WasmJsWriter",
101 ));
102 }
103
104 let sz = base64::engine::general_purpose::STANDARD
105 .encode_slice(&self.wasm_buf[..self.n], &mut self.out_buf)
106 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
107
108 if !self.started {
109 let opening = format!(
110 "import * as importObject from '{}';\nconst IMPORTS_KEY = '{}'\n;",
111 self.imports_module, self.imports_module
112 );
113 self.out.write_all(opening.to_os_bytes().as_ref())?;
114 self.out.write_all(PROLOG.as_ref())?;
115 self.started = true;
116 } else {
117 self.out.write_all(CHUNK_SEP.as_ref())?;
118 }
119 self.out.write_all(&self.out_buf[..sz])?;
120 self.n = 0;
121 Ok(())
122 }
123}
124
125impl<W: Write> Write for WasmJsWriter<W> {
126 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
127 if buf.len() == 0 {
128 return Ok(0);
129 }
130
131 let mut written = 0;
132 let mut space = CHUNK_WORDS * 3 - self.n;
133 while buf.len() - written > space {
134 self.wasm_buf[self.n..(self.n + space)]
135 .copy_from_slice(&buf[written..(written + space)]);
136 self.n += space;
137 written += space;
138 self.push_chunk()?;
139 space = CHUNK_WORDS * 3;
140 }
141 let sz = buf.len() - written;
142 self.wasm_buf[self.n..(self.n + sz)].copy_from_slice(&buf[written..]);
143 self.n += sz;
144 Ok(buf.len())
145 }
146
147 fn flush(&mut self) -> std::io::Result<()> {
148 if self.finished {
149 return Ok(());
150 }
151 self.push_chunk()?;
152 self.finished = true;
153 self.out.write_all(EPILOG.as_ref())?;
154 self.out.flush()?;
155 Ok(())
156 }
157}