matoya-worker.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. // This Source Code Form is subject to the terms of the MIT License.
  2. // If a copy of the MIT License was not distributed with this file,
  3. // You can obtain one at https://spdx.org/licenses/MIT.html.
  4. // Worker State
  5. const MTY = {
  6. keys: {},
  7. keysRev: {},
  8. glIndex: 0,
  9. glObj: {},
  10. fds: {},
  11. fdIndex: 0,
  12. };
  13. // Allocation
  14. function mty_cfunc(ptr) {
  15. return MTY.exports.__indirect_function_table.get(ptr);
  16. }
  17. function mty_alloc(size, el) {
  18. return MTY.exports.mty_system_alloc(size, el ? el : 1);
  19. }
  20. function mty_free(ptr) {
  21. MTY.exports.mty_system_free(ptr);
  22. }
  23. function mty_dup_c(buf) {
  24. const ptr = mty_alloc(buf.byteLength + 1);
  25. mty_memcpy(ptr, buf);
  26. return ptr;
  27. }
  28. // window.localStorage
  29. function mty_get_ls(key) {
  30. postMessage({
  31. type: 'get-ls',
  32. key: key,
  33. sab: MTY.sab,
  34. sync: MTY.sync,
  35. });
  36. mty_wait(MTY.sync);
  37. const size = MTY.sab[0];
  38. if (size == 0)
  39. return 0;
  40. const sab8 = new Uint8Array(new SharedArrayBuffer(size));
  41. postMessage({
  42. type: 'async-copy',
  43. sab8: sab8,
  44. sync: MTY.sync,
  45. });
  46. mty_wait(MTY.sync);
  47. return sab8;
  48. }
  49. function mty_set_ls(key, val) {
  50. postMessage({
  51. type: 'set-ls',
  52. key: key,
  53. val: val,
  54. sync: MTY.sync,
  55. });
  56. mty_wait(MTY.sync);
  57. }
  58. // <unistd.h> stubs
  59. const MTY_UNISTD_API = {
  60. flock: function (fd, flags) {
  61. return 0;
  62. },
  63. };
  64. // GL
  65. function mty_gl_new(obj) {
  66. MTY.glObj[MTY.glIndex] = obj;
  67. return MTY.glIndex++;
  68. }
  69. function mty_gl_del(index) {
  70. let obj = MTY.glObj[index];
  71. delete MTY.glObj[index];
  72. return obj;
  73. }
  74. function mty_gl_obj(index) {
  75. return MTY.glObj[index];
  76. }
  77. const MTY_GL_API = {
  78. glGenFramebuffers: function (n, ids) {
  79. for (let x = 0; x < n; x++)
  80. mty_set_uint32(ids + x * 4, mty_gl_new(MTY.gl.createFramebuffer()));
  81. },
  82. glDeleteFramebuffers: function (n, ids) {
  83. for (let x = 0; x < n; x++)
  84. MTY.gl.deleteFramebuffer(mty_gl_del(mty_get_uint32(ids + x * 4)));
  85. },
  86. glBindFramebuffer: function (target, fb) {
  87. MTY.gl.bindFramebuffer(target, fb ? mty_gl_obj(fb) : null);
  88. },
  89. glFramebufferTexture2D: function (target, attachment, textarget, texture, level) {
  90. MTY.gl.framebufferTexture2D(target, attachment, textarget, mty_gl_obj(texture), level);
  91. },
  92. glEnable: function (cap) {
  93. MTY.gl.enable(cap);
  94. },
  95. glDisable: function (cap) {
  96. MTY.gl.disable(cap);
  97. },
  98. glViewport: function (x, y, width, height) {
  99. MTY.gl.viewport(x, y, width, height);
  100. },
  101. glBindTexture: function (target, texture) {
  102. MTY.gl.bindTexture(target, texture ? mty_gl_obj(texture) : null);
  103. },
  104. glDeleteTextures: function (n, ids) {
  105. for (let x = 0; x < n; x++)
  106. MTY.gl.deleteTexture(mty_gl_del(mty_get_uint32(ids + x * 4)));
  107. },
  108. glTexParameteri: function (target, pname, param) {
  109. MTY.gl.texParameteri(target, pname, param);
  110. },
  111. glGenTextures: function (n, ids) {
  112. for (let x = 0; x < n; x++)
  113. mty_set_uint32(ids + x * 4, mty_gl_new(MTY.gl.createTexture()));
  114. },
  115. glTexImage2D: function (target, level, internalformat, width, height, border, format, type, data) {
  116. MTY.gl.texImage2D(target, level, internalformat, width, height, border, format, type,
  117. new Uint8Array(MTY_MEMORY.buffer, data));
  118. },
  119. glTexSubImage2D: function (target, level, xoffset, yoffset, width, height, format, type, pixels) {
  120. MTY.gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type,
  121. new Uint8Array(MTY_MEMORY.buffer, pixels));
  122. },
  123. glDrawElements: function (mode, count, type, indices) {
  124. MTY.gl.drawElements(mode, count, type, indices);
  125. },
  126. glGetAttribLocation: function (program, c_name) {
  127. return MTY.gl.getAttribLocation(mty_gl_obj(program), mty_str_to_js(c_name));
  128. },
  129. glShaderSource: function (shader, count, c_strings, c_len) {
  130. let source = '';
  131. for (let x = 0; x < count; x++)
  132. source += mty_str_to_js(mty_get_uint32(c_strings + x * 4));
  133. MTY.gl.shaderSource(mty_gl_obj(shader), source);
  134. },
  135. glBindBuffer: function (target, buffer) {
  136. MTY.gl.bindBuffer(target, buffer ? mty_gl_obj(buffer) : null);
  137. },
  138. glVertexAttribPointer: function (index, size, type, normalized, stride, pointer) {
  139. MTY.gl.vertexAttribPointer(index, size, type, normalized, stride, pointer);
  140. },
  141. glCreateProgram: function () {
  142. return mty_gl_new(MTY.gl.createProgram());
  143. },
  144. glUniform1i: function (loc, v0) {
  145. MTY.gl.uniform1i(mty_gl_obj(loc), v0);
  146. },
  147. glUniform1f: function (loc, v0) {
  148. MTY.gl.uniform1f(mty_gl_obj(loc), v0);
  149. },
  150. glUniform4i: function (loc, v0, v1, v2, v3) {
  151. MTY.gl.uniform4i(mty_gl_obj(loc), v0, v1, v2, v3);
  152. },
  153. glUniform4f: function (loc, v0, v1, v2, v3) {
  154. MTY.gl.uniform4f(mty_gl_obj(loc), v0, v1, v2, v3);
  155. },
  156. glActiveTexture: function (texture) {
  157. MTY.gl.activeTexture(texture);
  158. },
  159. glDeleteBuffers: function (n, ids) {
  160. for (let x = 0; x < n; x++)
  161. MTY.gl.deleteBuffer(mty_gl_del(mty_get_uint32(ids + x * 4)));
  162. },
  163. glEnableVertexAttribArray: function (index) {
  164. MTY.gl.enableVertexAttribArray(index);
  165. },
  166. glBufferData: function (target, size, data, usage) {
  167. MTY.gl.bufferData(target, new Uint8Array(MTY_MEMORY.buffer, data, size), usage);
  168. },
  169. glDeleteShader: function (shader) {
  170. MTY.gl.deleteShader(mty_gl_del(shader));
  171. },
  172. glGenBuffers: function (n, ids) {
  173. for (let x = 0; x < n; x++)
  174. mty_set_uint32(ids + x * 4, mty_gl_new(MTY.gl.createBuffer()));
  175. },
  176. glCompileShader: function (shader) {
  177. MTY.gl.compileShader(mty_gl_obj(shader));
  178. },
  179. glLinkProgram: function (program) {
  180. MTY.gl.linkProgram(mty_gl_obj(program));
  181. },
  182. glGetUniformLocation: function (program, name) {
  183. return mty_gl_new(MTY.gl.getUniformLocation(mty_gl_obj(program), mty_str_to_js(name)));
  184. },
  185. glCreateShader: function (type) {
  186. return mty_gl_new(MTY.gl.createShader(type));
  187. },
  188. glAttachShader: function (program, shader) {
  189. MTY.gl.attachShader(mty_gl_obj(program), mty_gl_obj(shader));
  190. },
  191. glUseProgram: function (program) {
  192. MTY.gl.useProgram(program ? mty_gl_obj(program) : null);
  193. },
  194. glGetShaderiv: function (shader, pname, params) {
  195. if (pname == 0x8B81) {
  196. let ok = MTY.gl.getShaderParameter(mty_gl_obj(shader), MTY.gl.COMPILE_STATUS);
  197. mty_set_uint32(params, ok);
  198. if (!ok)
  199. console.warn(MTY.gl.getShaderInfoLog(mty_gl_obj(shader)));
  200. } else {
  201. mty_set_uint32(params, 0);
  202. }
  203. },
  204. glDetachShader: function (program, shader) {
  205. MTY.gl.detachShader(mty_gl_obj(program), mty_gl_obj(shader));
  206. },
  207. glDeleteProgram: function (program) {
  208. MTY.gl.deleteProgram(mty_gl_del(program));
  209. },
  210. glClear: function (mask) {
  211. MTY.gl.clear(mask);
  212. },
  213. glClearColor: function (red, green, blue, alpha) {
  214. MTY.gl.clearColor(red, green, blue, alpha);
  215. },
  216. glGetError: function () {
  217. return MTY.gl.getError();
  218. },
  219. glGetShaderInfoLog: function (shader, maxLength, length, infoLog) {
  220. const log = gl.getShaderInfoLog(mty_gl_obj(shader));
  221. const buf = mty_encode(log);
  222. if (buf.length < maxLength) {
  223. mty_set_uint32(length);
  224. mty_strcpy(infoLog, buf);
  225. }
  226. },
  227. glFinish: function () {
  228. MTY.gl.finish();
  229. },
  230. glScissor: function (x, y, width, height) {
  231. MTY.gl.scissor(x, y, width, height);
  232. },
  233. glBlendFunc: function (sfactor, dfactor) {
  234. MTY.gl.blendFunc(sfactor, dfactor);
  235. },
  236. glBlendEquation: function (mode) {
  237. MTY.gl.blendEquation(mode);
  238. },
  239. glUniformMatrix4fv: function (loc, count, transpose, value) {
  240. MTY.gl.uniformMatrix4fv(mty_gl_obj(loc), transpose,
  241. new Float32Array(MTY_MEMORY.buffer, value, 4 * 4 * count));
  242. },
  243. glGetProgramiv: function (program, pname, params) {
  244. mty_set_uint32(params, MTY.gl.getProgramParameter(mty_gl_obj(program), pname));
  245. },
  246. glPixelStorei: function (pname, param) {
  247. MTY.gl.pixelStorei(pname, param);
  248. },
  249. web_gl_flush: function () {
  250. MTY.gl.flush();
  251. },
  252. };
  253. // Audio
  254. function mty_mutex_lock_nb(mutex, index)
  255. {
  256. return Atomics.compareExchange(mutex, index, 0, 1) == 0;
  257. }
  258. function mty_mutex_lock(mutex, index) {
  259. while (true) {
  260. if (mty_mutex_lock_nb(mutex, index))
  261. return;
  262. Atomics.wait(mutex, index, 1);
  263. }
  264. }
  265. function mty_mutex_unlock(mutex, index, notify) {
  266. Atomics.compareExchange(mutex, index, 1, 0);
  267. if (notify)
  268. Atomics.notify(mutex, index, 1);
  269. }
  270. const MTY_AUDIO_API = {
  271. MTY_AudioCreate: function (sampleRate, minBuffer, maxBuffer, channels, deviceID, fallback) {
  272. MTY.audio = {
  273. sampleRate,
  274. minBuffer,
  275. maxBuffer,
  276. channels,
  277. };
  278. return 0xCDD;
  279. },
  280. MTY_AudioDestroy: function (audio) {
  281. if (!audio || !mty_get_uint32(audio))
  282. return;
  283. postMessage({type: 'audio-destroy'});
  284. mty_set_uint32(audio, 0);
  285. },
  286. MTY_AudioQueue: function (ctx, frames, count) {
  287. const buf = new Int16Array(MTY_MEMORY.buffer, frames, count * MTY.audio.channels);
  288. mty_mutex_lock(MTY.audioObjs.control, 0);
  289. if (buf.length <= MTY.audioObjs.buf.length - MTY.audioObjs.control[1]) {
  290. MTY.audioObjs.buf.set(buf, MTY.audioObjs.control[1]);
  291. MTY.audioObjs.control[1] += buf.length;
  292. }
  293. mty_mutex_unlock(MTY.audioObjs.control, 0, false);
  294. postMessage({type: 'audio-queue', ...MTY.audio});
  295. },
  296. MTY_AudioReset: function (ctx) {
  297. mty_mutex_lock(MTY.audioObjs.control, 0);
  298. MTY.audioObjs.control[1] = 0;
  299. mty_mutex_unlock(MTY.audioObjs.control, 0, false);
  300. },
  301. MTY_AudioGetQueued: function (ctx) {
  302. return Atomics.load(MTY.audioObjs.control, 2);
  303. },
  304. };
  305. // Audio Worklet
  306. if (typeof AudioWorkletGlobalScope != 'undefined') {
  307. function mty_int16_to_float(i) {
  308. return i < 0 ? i / 32768 : i / 32767;
  309. }
  310. class MTY_Audio extends AudioWorkletProcessor {
  311. constructor(options) {
  312. super();
  313. const frames_per_ms = Math.round(sampleRate / 1000.0);
  314. this.minBuffer = Math.round(options.processorOptions.minBuffer * frames_per_ms);
  315. this.maxBuffer = Math.round(options.processorOptions.maxBuffer * frames_per_ms);
  316. this.channels = options.outputChannelCount[0];
  317. this.playing = false;
  318. this.ibuf = new Int16Array(new ArrayBuffer(1024 * 1024));
  319. this.ibufLen = 0;
  320. this.port.onmessage = (evt) => {
  321. this.sbuf = evt.data.buf;
  322. this.control = evt.data.control;
  323. };
  324. }
  325. process(inputs, outputs, parameters) {
  326. // Copy from staging buffer to internal buffer
  327. if (mty_mutex_lock_nb(this.control, 0)) {
  328. if (this.control[1] <= this.ibuf.length - this.ibufLen) {
  329. this.ibuf.set(new Int16Array(this.sbuf.buffer, 0, this.control[1]), this.ibufLen);
  330. this.ibufLen += this.control[1];
  331. this.control[1] = 0;
  332. }
  333. mty_mutex_unlock(this.control, 0, true);
  334. }
  335. let queued = this.ibufLen / this.channels;
  336. // Queued audio has reached the min buffer, begin playing
  337. if (!this.playing && queued >= this.minBuffer)
  338. this.playing = true;
  339. // No audio left, pause and let buffer refill
  340. if (this.playing && this.ibufLen == 0)
  341. this.playing = false;
  342. // Fill output with buffered audio
  343. if (this.playing) {
  344. const l = outputs[0][0];
  345. const r = outputs[0][1];
  346. let x = 0;
  347. for (let o = 0; x < this.ibufLen && o < l.length && o < r.length; x += this.channels, o++) {
  348. l[o] = mty_int16_to_float(this.ibuf[x]);
  349. r[o] = mty_int16_to_float(this.ibuf[x + 1]);
  350. }
  351. // Essentially a 'memmove' to bring remaining audio to front of the buffer
  352. this.ibufLen -= x;
  353. this.ibuf.set(new Int16Array(this.ibuf.buffer, x * 2, this.ibufLen));
  354. }
  355. queued = this.ibufLen / this.channels;
  356. // If buffer has exceeded the max, reset
  357. if (this.playing && queued > this.maxBuffer) {
  358. this.playing = false;
  359. this.ibufLen = 0;
  360. }
  361. // Store queued frames
  362. Atomics.store(this.control, 2, queued);
  363. return true;
  364. }
  365. }
  366. registerProcessor('MTY_Audio', MTY_Audio);
  367. }
  368. // Net
  369. function mty_net_headers(cheaders) {
  370. const headers_str = mty_str_to_js(cheaders);
  371. const headers = {};
  372. const headers_nl = headers_str.split('\n');
  373. for (let x = 0; x < headers_nl.length; x++) {
  374. const pair = headers_nl[x];
  375. const pair_split = pair.split(':');
  376. if (pair_split[0] && pair_split[1])
  377. headers[pair_split[0]] = pair_split[1];
  378. }
  379. return headers;
  380. }
  381. const MTY_NET_API = {
  382. MTY_HttpRequest: function (curl, cmethod, cheaders, cbody, bodySize, proxy, timeout,
  383. response, responseSize, cstatus)
  384. {
  385. // FIXME timeout is currently ignored
  386. // FIXME proxy is currently ignored
  387. const body = cbody ? mty_dup(cbody, bodySize) : null;
  388. postMessage({
  389. type: 'http',
  390. url: mty_str_to_js(curl),
  391. method: mty_str_to_js(cmethod),
  392. headers: mty_net_headers(cheaders),
  393. body: body,
  394. sync: MTY.sync,
  395. sab: MTY.sab,
  396. }, body ? [body.buffer] : []);
  397. mty_wait(MTY.sync);
  398. const error = MTY.sab[0];
  399. if (error)
  400. return false;
  401. const size = MTY.sab[1];
  402. mty_set_uint32(responseSize, size);
  403. const status = MTY.sab[2];
  404. mty_set_uint16(cstatus, status);
  405. if (size > 0) {
  406. const buf = mty_alloc(size + 1);
  407. mty_set_uint32(response, buf);
  408. postMessage({
  409. type: 'async-copy',
  410. sync: MTY.sync,
  411. sab8: new Uint8Array(MTY_MEMORY.buffer, buf, size + 1),
  412. });
  413. mty_wait(MTY.sync);
  414. }
  415. return true;
  416. },
  417. MTY_WebSocketConnect: function (curl, cheaders, proxy, timeout, upgrade_status_out) {
  418. // FIXME headers are currently ignored
  419. // FIXME proxy is currently ignored
  420. // FIXME timeout is currently ignored
  421. // FIXME upgrade_status_out currently unsupported
  422. postMessage({
  423. type: 'ws-connect',
  424. url: mty_str_to_js(curl),
  425. sync: MTY.sync,
  426. sab: MTY.sab,
  427. });
  428. mty_wait(MTY.sync);
  429. return MTY.sab[0];
  430. },
  431. MTY_WebSocketDestroy: function (ctx_out) {
  432. if (!ctx_out)
  433. return;
  434. postMessage({
  435. type: 'ws-close',
  436. ctx: mty_get_uint32(ctx_out),
  437. });
  438. },
  439. MTY_WebSocketRead: function (ctx, timeout, msg_out, size) {
  440. postMessage({
  441. type: 'ws-read',
  442. ctx: ctx,
  443. timeout: timeout,
  444. sab: MTY.sab,
  445. sync: MTY.sync,
  446. });
  447. mty_wait(MTY.sync);
  448. if (MTY.sab[0] == 0) { // MTY_ASYNC_OK
  449. const rsize = MTY.sab[1];
  450. if (rsize < size) {
  451. const buf = mty_alloc(rsize);
  452. const sab8 = new Uint8Array(MTY_MEMORY.buffer, buf, rsize);
  453. postMessage({
  454. type: 'async-copy',
  455. sync: MTY.sync,
  456. sab8: sab8,
  457. });
  458. mty_wait(MTY.sync);
  459. mty_strcpy(msg_out, sab8);
  460. mty_free(buf);
  461. } else {
  462. MTY.sab[0] = 3 // MTY_ASYNC_ERROR
  463. }
  464. }
  465. return MTY.sab[0]; // MTY_Async
  466. },
  467. MTY_WebSocketWrite: function (ctx, msg_c) {
  468. postMessage({
  469. type: 'ws-write',
  470. ctx: ctx,
  471. text: mty_str_to_js(msg_c),
  472. });
  473. return true;
  474. },
  475. MTY_WebSocketGetCloseCode: function (ctx) {
  476. postMessage({
  477. type: 'ws-code',
  478. ctx: ctx,
  479. sab: MTY.sab,
  480. sync: MTY.sync,
  481. });
  482. mty_wait(MTY.sync);
  483. return MTY.sab[0];
  484. },
  485. };
  486. // Image
  487. const MTY_IMAGE_API = {
  488. MTY_DecompressImage: function (input, size, cwidth, cheight) {
  489. const jinput = mty_dup(input, size);
  490. postMessage({
  491. type: 'decode-image',
  492. input: jinput.buffer,
  493. sync: MTY.sync,
  494. sab: MTY.sab,
  495. }, [jinput.buffer]);
  496. mty_wait(MTY.sync);
  497. const width = MTY.sab[0];
  498. mty_set_uint32(cwidth, width);
  499. const height = MTY.sab[1];
  500. mty_set_uint32(cheight, height);
  501. const buf_size = width * height * 4;
  502. const buf = mty_alloc(buf_size);
  503. postMessage({
  504. type: 'async-copy',
  505. sync: MTY.sync,
  506. sab8: new Uint8Array(MTY_MEMORY.buffer, buf, buf_size),
  507. });
  508. mty_wait(MTY.sync);
  509. return buf;
  510. },
  511. MTY_CompressImage: function (method, input, width, height, outputSize) {
  512. },
  513. MTY_GetProgramIcon: function (path, width, height) {
  514. },
  515. };
  516. // Crypto
  517. const MTY_CRYPTO_API = {
  518. MTY_CryptoHash: function (algo, input, inputSize, key, keySize, output, outputSize) {
  519. },
  520. MTY_GetRandomBytes: function (buf, size) {
  521. mty_memcpy(buf, crypto.getRandomValues(new Uint8Array(size)));
  522. },
  523. MTY_BytesToBase64: function (bytes, size, base64, base64Size) {
  524. const jbytes = new Uint8Array(MTY_MEMORY.buffer, bytes, size);
  525. try {
  526. mty_str_to_c(mty_buf_to_b64(jbytes), base64, base64Size);
  527. } catch (e) {
  528. console.error("'base64Size' is too small");
  529. }
  530. },
  531. };
  532. // System
  533. const MTY_SYSTEM_API = {
  534. MTY_HandleProtocol: function (uri, token) {
  535. postMessage({type: 'uri', uri});
  536. },
  537. };
  538. // Web API (mostly used in app.c)
  539. function mty_update_window(app, info) {
  540. MTY.exports.mty_window_update_position(app, info.posX, info.posY);
  541. MTY.exports.mty_window_update_screen(app, info.screenWidth, info.screenHeight);
  542. MTY.exports.mty_window_update_size(app, info.canvasWidth, info.canvasHeight);
  543. MTY.exports.mty_window_update_focus(app, info.hasFocus);
  544. MTY.exports.mty_window_update_fullscreen(app, info.fullscreen);
  545. MTY.exports.mty_window_update_visibility(app, info.visible);
  546. MTY.exports.mty_window_update_pixel_ratio(app, info.devicePixelRatio);
  547. MTY.exports.mty_window_update_relative_mouse(app, info.relative);
  548. }
  549. const MTY_WEB_API = {
  550. web_alert: function (title, msg) {
  551. postMessage({type: 'alert', title, msg});
  552. },
  553. web_set_fullscreen: function (fullscreen) {
  554. postMessage({type: 'fullscreen', fullscreen});
  555. },
  556. web_wake_lock: function (enable) {
  557. postMessage({type: 'wake-lock', enable});
  558. },
  559. web_rumble_gamepad: function (id, low, high) {
  560. postMessage({type: 'rumble', id, low, high});
  561. },
  562. web_show_cursor: function (show) {
  563. postMessage({type: 'show-cursor', show});
  564. },
  565. web_get_clipboard: function () {
  566. postMessage({type: 'get-clip', sync: MTY.sync, sab: MTY.sab});
  567. mty_wait(MTY.sync);
  568. const size = MTY.sab[0];
  569. const buf = mty_alloc(size + 1);
  570. if (size > 0) {
  571. postMessage({
  572. type: 'async-copy',
  573. sync: MTY.sync,
  574. sab8: new Uint8Array(MTY_MEMORY.buffer, buf, size + 1),
  575. });
  576. mty_wait(MTY.sync);
  577. }
  578. return buf;
  579. },
  580. web_set_clipboard: function (text) {
  581. postMessage({type: 'set-clip', text});
  582. },
  583. web_set_pointer_lock: function (enable) {
  584. postMessage({type: 'pointer-lock', enable});
  585. },
  586. web_use_default_cursor: function (use_default) {
  587. postMessage({type: 'cursor-default', use_default});
  588. },
  589. web_set_rgba_cursor: function (buffer, width, height, hot_x, hot_y) {
  590. const buf = buffer ? mty_dup(buffer, width * height * 4) : null
  591. postMessage({type: 'cursor-rgba', buf, width, height, hot_x, hot_y}, buf ? [buf.buffer] : []);
  592. },
  593. web_set_png_cursor: function (buffer, size, hot_x, hot_y) {
  594. const buf = buffer ? mty_dup(buffer, size) : null
  595. postMessage({type: 'cursor-png', buf, hot_x, hot_y}, buf ? [buf.buffer] : []);
  596. },
  597. web_set_kb_grab: function (grab) {
  598. postMessage({type: 'kb-grab', grab});
  599. },
  600. web_get_hostname: function () {
  601. return mty_dup_c(mty_encode(MTY.hostname));
  602. },
  603. web_platform: function (platform, size) {
  604. mty_str_to_c(navigator.platform, platform, size);
  605. },
  606. web_set_key: function (reverse, code, key) {
  607. const str = mty_str_to_js(code);
  608. MTY.keys[str] = key;
  609. if (reverse)
  610. MTY.keysRev[key] = str;
  611. },
  612. web_get_key: function (key, buf, len) {
  613. const code = MTY.keysRev[key];
  614. if (code != undefined) {
  615. const text = MTY.kbMap[code];
  616. if (text) {
  617. mty_str_to_c(text.toUpperCase(), buf, len);
  618. } else {
  619. mty_str_to_c(code, buf, len);
  620. }
  621. return true;
  622. }
  623. return false;
  624. },
  625. web_set_title: function (title) {
  626. postMessage({
  627. type: 'title',
  628. title: mty_str_to_js(title),
  629. });
  630. },
  631. web_set_gfx: function () {
  632. const info = MTY.initWindowInfo;
  633. const canvas = new OffscreenCanvas(info.canvasWidth, info.canvasHeight);
  634. MTY.gl = canvas.getContext('webgl2', {
  635. depth: false,
  636. antialias: false,
  637. powerPreference: 'high-performance',
  638. });
  639. },
  640. web_set_canvas_size: function (width, height) {
  641. MTY.gl.canvas.width = width;
  642. MTY.gl.canvas.height = height;
  643. },
  644. web_present: function (wait) {
  645. const image = MTY.gl.canvas.transferToImageBitmap();
  646. postMessage({
  647. type: 'present',
  648. image: image,
  649. }, [image]);
  650. if (wait)
  651. mty_wait(MTY.psync);
  652. },
  653. // Synchronization from C
  654. MTY_WaitPtr: function (csync) {
  655. mty_wait(new Int32Array(MTY_MEMORY.buffer, csync, 1));
  656. },
  657. // Should be called on main thread only
  658. web_set_app: function (app) {
  659. MTY.app = app;
  660. mty_update_window(app, MTY.initWindowInfo);
  661. },
  662. web_run_and_yield: function (iter, opaque) {
  663. MTY.exports.mty_app_set_keys();
  664. const step = () => {
  665. if (mty_cfunc(iter)(opaque))
  666. setTimeout(step, 0);
  667. };
  668. setTimeout(step, 0);
  669. throw 'MTY_RunAndYield halted execution';
  670. },
  671. };
  672. // WASI API
  673. // github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/wasi/api.h
  674. const __WASI_ERRNO_SUCCESS = 0;
  675. const __WASI_ERRNO_BADF = 8;
  676. const __WASI_ERRNO_INVAL = 28;
  677. function mty_append_buf(cur_buf, buf) {
  678. // FIXME This is a crude way to handle appending to an open file,
  679. // complex seek operations will break this
  680. const new_buf = new Uint8Array(cur_buf.length + buf.length);
  681. new_buf.set(cur_buf);
  682. new_buf.set(buf, cur_buf.length);
  683. return new_buf;
  684. }
  685. function mty_arg_list(bin, args) {
  686. let plist = [mty_encode(bin)];
  687. const params = new URLSearchParams(args);
  688. const qs = params.toString();
  689. if (qs)
  690. plist.push(mty_encode(qs));
  691. return plist;
  692. }
  693. const MTY_WASI_SNAPSHOT_PREVIEW1_API = {
  694. // Command line arguments
  695. args_get: function (argv, argv_buf) {
  696. const args = mty_arg_list(MTY.bin, MTY.queryString);
  697. for (let x = 0; x < args.length; x++) {
  698. mty_strcpy(argv_buf, args[x]);
  699. mty_set_uint32(argv + x * 4, argv_buf);
  700. argv_buf += args[x].length + 1;
  701. }
  702. return __WASI_ERRNO_SUCCESS;
  703. },
  704. args_sizes_get: function (retptr0, retptr1) {
  705. const args = mty_arg_list(MTY.bin, MTY.queryString);
  706. let len = 0;
  707. for (let x = 0; x < args.length; x++)
  708. len += args[x].length + 1;
  709. mty_set_uint32(retptr0, args.length);
  710. mty_set_uint32(retptr1, len);
  711. return __WASI_ERRNO_SUCCESS;
  712. },
  713. // WASI preopened directory (/)
  714. fd_prestat_get: function (fd, retptr0) {
  715. if (MTY.preopen == undefined) {
  716. mty_set_int8(retptr0, 0);
  717. mty_set_uint64(retptr0 + 4, 1);
  718. MTY.preopen = fd;
  719. return __WASI_ERRNO_SUCCESS;
  720. }
  721. return __WASI_ERRNO_BADF;
  722. },
  723. fd_prestat_dir_name: function (fd, path, path_len) {
  724. if (MTY.preopen == fd) {
  725. mty_strcpy(path, mty_encode('/'));
  726. return __WASI_ERRNO_SUCCESS;
  727. }
  728. return __WASI_ERRNO_INVAL;
  729. },
  730. // Paths
  731. path_filestat_get: function (fd, flags, path, path_size, retptr0) {
  732. const jpath = mty_str_to_js(path);
  733. const buf = mty_get_ls(jpath);
  734. // We only need to return the size
  735. if (buf)
  736. mty_set_uint64(retptr0 + 32, buf.byteLength);
  737. return __WASI_ERRNO_SUCCESS;
  738. },
  739. path_open: function (fd, dirflags, path, path_size, oflags, fs_rights_base,
  740. fs_rights_inheriting, fdflags, retptr0)
  741. {
  742. const new_fd = MTY.fdIndex++;
  743. mty_set_uint32(retptr0, new_fd);
  744. MTY.fds[new_fd] = {
  745. path: mty_str_to_js(path),
  746. append: fdflags == 1,
  747. offset: 0,
  748. };
  749. return __WASI_ERRNO_SUCCESS;
  750. },
  751. path_create_directory: function (fd, path) {
  752. return __WASI_ERRNO_SUCCESS;
  753. },
  754. path_remove_directory: function (fd, path) {
  755. return __WASI_ERRNO_SUCCESS;
  756. },
  757. path_unlink_file: function (fd, path) {
  758. return __WASI_ERRNO_SUCCESS;
  759. },
  760. path_readlink: function (fd, path, buf, buf_len, retptr0) {
  761. },
  762. path_rename: function (fd, old_path, new_fd, new_path) {
  763. return __WASI_ERRNO_SUCCESS;
  764. },
  765. // File descriptors
  766. fd_close: function (fd) {
  767. delete MTY.fds[fd];
  768. },
  769. fd_fdstat_get: function (fd, retptr0) {
  770. return __WASI_ERRNO_SUCCESS;
  771. },
  772. fd_fdstat_set_flags: function (fd, flags) {
  773. },
  774. fd_readdir: function (fd, buf, buf_len, cookie, retptr0) {
  775. return __WASI_ERRNO_BADF;
  776. },
  777. fd_seek: function (fd, offset, whence, retptr0) {
  778. return __WASI_ERRNO_SUCCESS;
  779. },
  780. fd_read: function (fd, iovs, iovs_len, retptr0) {
  781. const finfo = MTY.fds[fd];
  782. const file_buf = mty_get_ls(finfo.path);
  783. if (finfo && file_buf) {
  784. let offset = 0;
  785. for (let x = 0; x < iovs_len; x++) {
  786. let ptr = iovs + x * 8;
  787. let buf = mty_get_uint32(ptr);
  788. let buf_len = mty_get_uint32(ptr + 4);
  789. let len = buf_len < file_buf.length - offset ? buf_len : file_buf.length - offset;
  790. mty_memcpy(buf, new Uint8Array(file_buf.buffer, offset, len));
  791. offset += len;
  792. }
  793. mty_set_uint32(retptr0, offset);
  794. }
  795. return __WASI_ERRNO_SUCCESS;
  796. },
  797. fd_write: function (fd, iovs, iovs_len, retptr0) {
  798. // Calculate full write size
  799. let len = 0;
  800. for (let x = 0; x < iovs_len; x++)
  801. len += mty_get_uint32(iovs + x * 8 + 4);
  802. mty_set_uint32(retptr0, len);
  803. // Create a contiguous buffer
  804. let offset = 0;
  805. let file_buf = new Uint8Array(len);
  806. for (let x = 0; x < iovs_len; x++) {
  807. let ptr = iovs + x * 8;
  808. let buf = mty_get_uint32(ptr);
  809. let buf_len = mty_get_uint32(ptr + 4);
  810. file_buf.set(new Uint8Array(MTY_MEMORY.buffer, buf, buf_len), offset);
  811. offset += buf_len;
  812. }
  813. // stdout
  814. if (fd == 1) {
  815. const str = mty_decode(file_buf);
  816. if (str != '\n')
  817. console.log(str);
  818. // stderr
  819. } else if (fd == 2) {
  820. const str = mty_decode(file_buf)
  821. if (str != '\n')
  822. console.error(str);
  823. // Filesystem
  824. } else if (MTY.fds[fd]) {
  825. const finfo = MTY.fds[fd];
  826. const cur_buf = mty_get_ls(finfo.path);
  827. if (cur_buf && finfo.append) {
  828. mty_set_ls(finfo.path, mty_append_buf(cur_buf, file_buf));
  829. } else {
  830. mty_set_ls(finfo.path, file_buf);
  831. }
  832. finfo.offet += len;
  833. }
  834. return __WASI_ERRNO_SUCCESS;
  835. },
  836. // Misc
  837. clock_time_get: function (id, precision, retptr0) {
  838. mty_set_uint64(retptr0, Math.round(performance.now() * 1000.0 * 1000.0));
  839. return __WASI_ERRNO_SUCCESS;
  840. },
  841. poll_oneoff: function (_in, out, nsubscriptions, retptr0) {
  842. // __WASI_EVENTTYPE_CLOCK
  843. if (mty_get_uint8(_in + 8) == 0)
  844. Atomics.wait(MTY.sleeper, 0, 0, Number(mty_get_uint64(_in + 24)) / 1000000);
  845. mty_set_uint32(out + 8, 0);
  846. return __WASI_ERRNO_SUCCESS;
  847. },
  848. proc_exit: function (rval) {
  849. },
  850. environ_get: function (environ, environ_buf) {
  851. },
  852. environ_sizes_get: function (retptr0, retptr1) {
  853. },
  854. sched_yield: function () {
  855. },
  856. };
  857. const MTY_WASI_API = {
  858. 'thread-spawn': function (start_arg) {
  859. postMessage({
  860. type: 'thread',
  861. startArg: start_arg,
  862. sab: MTY.sab,
  863. sync: MTY.sync,
  864. });
  865. mty_wait(MTY.sync);
  866. return MTY.sab[0];
  867. },
  868. };
  869. // Entry
  870. if (typeof WorkerGlobalScope != 'undefined') {
  871. async function mty_instantiate_wasm(wasmBuf, userEnv) {
  872. // Imports
  873. const imports = {
  874. env: {
  875. memory: MTY_MEMORY,
  876. ...MTY_UNISTD_API,
  877. ...MTY_GL_API,
  878. ...MTY_AUDIO_API,
  879. ...MTY_NET_API,
  880. ...MTY_IMAGE_API,
  881. ...MTY_CRYPTO_API,
  882. ...MTY_SYSTEM_API,
  883. ...MTY_WEB_API,
  884. },
  885. wasi_snapshot_preview1: {
  886. ...MTY_WASI_SNAPSHOT_PREVIEW1_API,
  887. },
  888. wasi: {
  889. ...MTY_WASI_API,
  890. },
  891. }
  892. // Add userEnv to imports, run on the main thread
  893. for (let x = 0; x < userEnv.length; x++) {
  894. const key = userEnv[x];
  895. imports.env[key] = function () {
  896. const args = [];
  897. for (let y = 0; y < arguments.length; y++)
  898. args.push(arguments[y]);
  899. postMessage({
  900. type: 'user-env',
  901. name: key,
  902. args: args,
  903. sab: MTY.sab,
  904. sync: MTY.sync,
  905. });
  906. mty_wait(MTY.sync);
  907. return MTY.sab[0];
  908. };
  909. }
  910. return await WebAssembly.instantiate(wasmBuf, imports);
  911. }
  912. onmessage = async (ev) => {
  913. const msg = ev.data;
  914. switch (msg.type) {
  915. case 'init':
  916. importScripts(msg.file);
  917. MTY_MEMORY = msg.memory;
  918. MTY.queryString = msg.args;
  919. MTY.hostname = msg.hostname;
  920. MTY.bin = msg.bin;
  921. MTY.fdIndex = 64;
  922. MTY.kbMap = msg.kbMap;
  923. MTY.psync = msg.psync;
  924. MTY.audioObjs = msg.audioObjs;
  925. MTY.initWindowInfo = msg.windowInfo;
  926. MTY.sync = new Int32Array(new SharedArrayBuffer(4));
  927. MTY.sleeper = new Int32Array(new SharedArrayBuffer(4));
  928. MTY.module = await mty_instantiate_wasm(msg.wasmBuf, msg.userEnv);
  929. MTY.exports = MTY.module.instance.exports;
  930. MTY.sab = new Uint32Array(new SharedArrayBuffer(128));
  931. // WASI will buffer stdout and stderr by default, disable it
  932. MTY.exports.mty_system_disable_buffering();
  933. try {
  934. // Additional thread
  935. if (msg.startArg) {
  936. MTY.exports.wasi_thread_start(msg.threadId, msg.startArg);
  937. // Main thread
  938. } else {
  939. MTY.exports._start();
  940. }
  941. close();
  942. } catch (e) {
  943. if (e.toString().search('MTY_RunAndYield') == -1)
  944. console.error(e);
  945. }
  946. break;
  947. // "Main" thread only
  948. case 'window-update':
  949. if (MTY.app)
  950. mty_update_window(MTY.app, msg.windowInfo);
  951. break;
  952. case 'keyboard': {
  953. if (!MTY.app)
  954. return;
  955. const key = MTY.keys[msg.code];
  956. if (key != undefined) {
  957. let packed = 0;
  958. if (msg.key.length == 1) {
  959. const buf = mty_encode(msg.key);
  960. for (let x = 0; x < buf.length; x++)
  961. packed |= buf[x] << x * 8;
  962. }
  963. MTY.exports.mty_window_keyboard(MTY.app, msg.pressed, key, packed, msg.mods);
  964. }
  965. break;
  966. }
  967. case 'motion':
  968. if (MTY.app)
  969. MTY.exports.mty_window_motion(MTY.app, msg.relative, msg.x, msg.y);
  970. break;
  971. case 'button':
  972. if (MTY.app)
  973. MTY.exports.mty_window_button(MTY.app, msg.pressed, msg.button, msg.x, msg.y);
  974. break;
  975. case 'scroll':
  976. if (MTY.app)
  977. MTY.exports.mty_window_scroll(MTY.app, msg.x, msg.y);
  978. break;
  979. case 'move':
  980. if (MTY.app)
  981. MTY.exports.mty_window_move(MTY.app);
  982. break;
  983. case 'size':
  984. if (MTY.app) {
  985. MTY.exports.mty_window_update_size(MTY.app, msg.width, msg.height);
  986. MTY.exports.mty_window_size(MTY.app);
  987. }
  988. break;
  989. case 'focus':
  990. if (MTY.app) {
  991. MTY.exports.mty_window_update_focus(MTY.app, msg.focus);
  992. MTY.exports.mty_window_focus(MTY.app, msg.focus);
  993. }
  994. break;
  995. case 'controller':
  996. if (MTY.app)
  997. MTY.exports.mty_window_controller(MTY.app, msg.id, msg.state, msg.buttons, msg.lx,
  998. msg.ly, msg.rx, msg.ry, msg.lt, msg.rt);
  999. break;
  1000. case 'controller-disconnect':
  1001. if (MTY.app)
  1002. MTY.exports.mty_window_controller(MTY.app, msg.id, msg.state, 0, 0, 0, 0, 0, 0, 0);
  1003. break;
  1004. case 'drop': {
  1005. if (!MTY.app)
  1006. return;
  1007. const cmem = mty_dup_c(new Uint8Array(msg.data));
  1008. const cname = mty_dup_c(mty_encode(msg.name));
  1009. MTY.exports.mty_window_drop(MTY.app, cname, cmem, buf.length);
  1010. mty_free(cname);
  1011. mty_free(cmem);
  1012. break;
  1013. }
  1014. }
  1015. };
  1016. }