working using seperate threads

This commit is contained in:
Gabriel Dinner-David
2024-11-24 00:26:42 -05:00
parent b90eef7861
commit 7d7e5e8196
11 changed files with 989 additions and 125 deletions

View File

@ -1,11 +1,11 @@
import { importObject, zjs } from "./imports"; import { importObject, setFiles, setStdin, setWasmModule, zjs } from "./imports";
import { old } from "./old"; import { old } from "./old";
const url = new URL("ghostty-wasm.wasm", import.meta.url); const url = new URL("ghostty-wasm.wasm", import.meta.url);
fetch(url.href) fetch(url.href)
.then((response) => response.arrayBuffer()) .then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, importObject)) .then((bytes) => WebAssembly.instantiate(bytes, importObject))
.then((results) => { .then(async (results) => {
const memory = importObject.env.memory; const memory = importObject.env.memory;
const { const {
atlas_clear, atlas_clear,
@ -39,6 +39,7 @@ fetch(url.href)
shared_grid_index_for_codepoint, shared_grid_index_for_codepoint,
shared_grid_render_glyph, shared_grid_render_glyph,
run, run,
draw,
} = results.instance.exports; } = results.instance.exports;
// Give us access to the zjs value for debugging. // Give us access to the zjs value for debugging.
@ -59,5 +60,42 @@ fetch(url.href)
// Create our config // Create our config
const config_str = makeStr("font-family = monospace\nfont-size = 32\n"); const config_str = makeStr("font-family = monospace\nfont-size = 32\n");
old(results); old(results);
setWasmModule(results.module);
const stdin = new SharedArrayBuffer(1024);
const files = {
nextFd: new SharedArrayBuffer(4),
polling: new SharedArrayBuffer(4),
has: new SharedArrayBuffer(1024),
}
new Int32Array(files.nextFd)[0] = 4;
setFiles(files);
setStdin(stdin);
run(config_str.ptr, config_str.len); run(config_str.ptr, config_str.len);
await new Promise((resolve) => setTimeout(resolve, 500))
const io = new Uint8ClampedArray(stdin, 4);
const text = new TextEncoder().encode("hello world\n\r");
io.set(text);
const n = new Int32Array(stdin);
console.error("storing");
Atomics.store(n, 0, text.length)
Atomics.notify(n, 0);
console.error("done storing");
function drawing() {
setTimeout(() => {
draw();
drawing();
}, 100);
}
drawing()
setTimeout(() => {
const io = new Uint8ClampedArray(stdin, 4);
const text = new TextEncoder().encode("🐏\n\r👍🏽\n\rM_ghostty\033[2;2H\033[48;2;240;40;40m\033[38;2;23;255;80mhello");
io.set(text);
const n = new Int32Array(stdin);
console.error("storing");
Atomics.store(n, 0, text.length)
Atomics.notify(n, 0);
console.error("done storing");
}, 5000)
}) })

View File

@ -1,13 +1,142 @@
import { ZigJS } from "zig-js/src/index.ts"; import { ZigJS } from "zig-js/src/index.ts";
import {
WASI_ESUCCESS,
WASI_EBADF,
WASI_EINVAL,
WASI_ENOSYS,
WASI_EPERM,
//WASI_ENOTCAPABLE,
WASI_FILETYPE_UNKNOWN,
WASI_FILETYPE_BLOCK_DEVICE,
WASI_FILETYPE_CHARACTER_DEVICE,
WASI_FILETYPE_DIRECTORY,
WASI_FILETYPE_REGULAR_FILE,
WASI_FILETYPE_SOCKET_STREAM,
WASI_FILETYPE_SYMBOLIC_LINK,
WASI_FILETYPE,
WASI_FDFLAG_APPEND,
WASI_FDFLAG_DSYNC,
WASI_FDFLAG_NONBLOCK,
WASI_FDFLAG_RSYNC,
WASI_FDFLAG_SYNC,
WASI_RIGHT_FD_DATASYNC,
WASI_RIGHT_FD_READ,
WASI_RIGHT_FD_SEEK,
WASI_RIGHT_FD_FDSTAT_SET_FLAGS,
WASI_RIGHT_FD_SYNC,
WASI_RIGHT_FD_TELL,
WASI_RIGHT_FD_WRITE,
WASI_RIGHT_FD_ADVISE,
WASI_RIGHT_FD_ALLOCATE,
WASI_RIGHT_PATH_CREATE_DIRECTORY,
WASI_RIGHT_PATH_CREATE_FILE,
WASI_RIGHT_PATH_LINK_SOURCE,
WASI_RIGHT_PATH_LINK_TARGET,
WASI_RIGHT_PATH_OPEN,
WASI_RIGHT_FD_READDIR,
WASI_RIGHT_PATH_READLINK,
WASI_RIGHT_PATH_RENAME_SOURCE,
WASI_RIGHT_PATH_RENAME_TARGET,
WASI_RIGHT_PATH_FILESTAT_GET,
WASI_RIGHT_PATH_FILESTAT_SET_SIZE,
WASI_RIGHT_PATH_FILESTAT_SET_TIMES,
WASI_RIGHT_FD_FILESTAT_GET,
WASI_RIGHT_FD_FILESTAT_SET_SIZE,
WASI_RIGHT_FD_FILESTAT_SET_TIMES,
WASI_RIGHT_PATH_SYMLINK,
WASI_RIGHT_PATH_REMOVE_DIRECTORY,
WASI_RIGHT_POLL_FD_READWRITE,
WASI_RIGHT_PATH_UNLINK_FILE,
RIGHTS_BLOCK_DEVICE_BASE,
RIGHTS_BLOCK_DEVICE_INHERITING,
RIGHTS_CHARACTER_DEVICE_BASE,
RIGHTS_CHARACTER_DEVICE_INHERITING,
RIGHTS_REGULAR_FILE_BASE,
RIGHTS_REGULAR_FILE_INHERITING,
RIGHTS_DIRECTORY_BASE,
RIGHTS_DIRECTORY_INHERITING,
RIGHTS_SOCKET_BASE,
RIGHTS_SOCKET_INHERITING,
RIGHTS_TTY_BASE,
RIGHTS_TTY_INHERITING,
WASI_CLOCK_MONOTONIC,
WASI_CLOCK_PROCESS_CPUTIME_ID,
WASI_CLOCK_REALTIME,
WASI_CLOCK_THREAD_CPUTIME_ID,
WASI_EVENTTYPE_CLOCK,
WASI_EVENTTYPE_FD_READ,
WASI_EVENTTYPE_FD_WRITE,
WASI_FILESTAT_SET_ATIM,
WASI_FILESTAT_SET_ATIM_NOW,
WASI_FILESTAT_SET_MTIM,
WASI_FILESTAT_SET_MTIM_NOW,
WASI_O_CREAT,
WASI_O_DIRECTORY,
WASI_O_EXCL,
WASI_O_TRUNC,
WASI_PREOPENTYPE_DIR,
WASI_STDIN_FILENO,
WASI_STDOUT_FILENO,
WASI_STDERR_FILENO,
ERROR_MAP,
SIGNAL_MAP,
WASI_WHENCE_CUR,
WASI_WHENCE_END,
WASI_WHENCE_SET,
} from "./wasi";
const textDecoder = new TextDecoder("utf-8"); const textDecoder = new TextDecoder("utf-8");
let stdin = new SharedArrayBuffer(1024);
export function setStdin(buf: SharedArrayBuffer) {
stdin = buf;
}
let bytes: null | Uint8ClampedArray = null
function perfNow() {
return performance.now() + performance.timeOrigin;
}
function readStdin() {
const len = new Int32Array(stdin);
if (len[0] == 0) {
Atomics.wait(len, 0, 0, 1000);
}
const length = len[0];
console.error("stdin", length);
if (length === 0) {
bytes = null;
return;
}
bytes = new Uint8ClampedArray(stdin, 4, length).slice();
console.log(textDecoder.decode(bytes));
Atomics.store(len, 0, 0);
Atomics.notify(len, 0);
}
function sleep(ms: number) {
const buf = new SharedArrayBuffer(4);
const view = new Int32Array(buf);
view[0] = 1;
Atomics.wait(view, 0, 1, ms);
}
let wasmModule;
export function setWasmModule(mod) {
wasmModule = mod;
}
let mainThread = true;
export function setMainThread(isMain) {
mainThread = isMain;
}
let gl: WebGL2RenderingContext; let gl: WebGL2RenderingContext;
export function setGl(l) { export function setGl(l) {
gl = l; gl = l;
} }
export const zjs = new ZigJS(); export const zjs = new ZigJS();
globalThis.fontCanvas = new OffscreenCanvas(0, 0);
// window.fontContext = () => {
// const ctx = fontCanvas.getContext("2d");
// ctx.willReadFrequently = true;
// return ctx;
// }
try { try {
const $webgl = document.getElementById("main-canvas"); const $webgl = document.getElementById("main-canvas");
let webgl2Supported = typeof WebGL2RenderingContext !== "undefined"; let webgl2Supported = typeof WebGL2RenderingContext !== "undefined";
@ -28,7 +157,7 @@ try {
throw new Error("The browser supports WebGL2, but initialization failed."); throw new Error("The browser supports WebGL2, but initialization failed.");
} }
} }
} catch (e){console.error(e) } } catch (e) { console.error(e) }
// OpenGL operates on numeric IDs while WebGL on objects. The following is a // OpenGL operates on numeric IDs while WebGL on objects. The following is a
// hack made to allow keeping current API on the native side while resolving IDs // hack made to allow keeping current API on the native side while resolving IDs
@ -230,7 +359,7 @@ const glBufferData = (type, count, pointer, drawType) => {
// The Float32Array multiplies by size of float which is 4, and the call to // The Float32Array multiplies by size of float which is 4, and the call to
// this method, due to OpenGL compatibility, also receives already // this method, due to OpenGL compatibility, also receives already
// pre-multiplied value. // pre-multiplied value.
gl.bufferData(type, zjs.memory.buffer.slice(pointer, pointer+count), drawType); gl.bufferData(type, zjs.memory.buffer.slice(pointer, pointer + count), drawType);
}; };
const glBufferSubData = (target, offset, size, data) => { const glBufferSubData = (target, offset, size, data) => {
@ -506,6 +635,39 @@ const webgl = {
glDrawElementsInstanced, glDrawElementsInstanced,
glBindBufferBase, glBindBufferBase,
} }
const nsToMs = (ns: number | bigint) => {
if (typeof ns === "number") {
ns = Math.trunc(ns);
}
const nsInt = BigInt(ns);
return Number(nsInt / BigInt(1000000));
};
const msToNs = (ms: number) => {
const msInt = Math.trunc(ms);
const decimal = BigInt(Math.round((ms - msInt) * 1000000));
const ns = BigInt(msInt) * BigInt(1000000);
return ns + decimal;
};
const CPUTIME_START = msToNs(perfNow());
const now = (clockId?: number) => {
switch (clockId) {
case WASI_CLOCK_MONOTONIC:
return msToNs(perfNow());
case WASI_CLOCK_REALTIME:
return msToNs(Date.now());
case WASI_CLOCK_PROCESS_CPUTIME_ID:
case WASI_CLOCK_THREAD_CPUTIME_ID: // TODO -- this assumes 1 thread
return msToNs(perfNow()) - CPUTIME_START;
default:
return null;
}
};
let files: { polling: SharedArrayBuffer, nextFd: SharedArrayBuffer, has: SharedArrayBuffer };
export function setFiles(file) {
files = file;
}
const fdStart = 4;
export const importObject = { export const importObject = {
module: {}, module: {},
env: { env: {
@ -521,6 +683,11 @@ export const importObject = {
const str = textDecoder.decode(data); const str = textDecoder.decode(data);
console.error(str); console.error(str);
}, },
eventFd() {
const next = new Int32Array(files.nextFd);
return Atomics.add(next, 0, 1);
},
fork: (...params) => { fork: (...params) => {
console.error("fork", params); console.error("fork", params);
}, },
@ -555,6 +722,20 @@ export const importObject = {
}, },
wasi_snapshot_preview1: { wasi_snapshot_preview1: {
fd_write: (fd, iovs, iovs_len, nwritten_ptr) => { fd_write: (fd, iovs, iovs_len, nwritten_ptr) => {
if (fd >= fdStart) {
const memory = new DataView(zjs.memory.buffer);
let nwritten = 0;
for (let offset = iovs; offset < iovs + iovs_len * 8; offset += 8) {
const iov_len = memory.getUint32(offset + 4, true);
nwritten += iov_len;
}
const has = new Int32Array(files.has);
Atomics.store(has, fd - fdStart, 1);
Atomics.notify(has, fd - fdStart);
console.error("notify", fd);
memory.setUint32(nwritten_ptr, nwritten, true);
} else {
const memory = new DataView(zjs.memory.buffer); const memory = new DataView(zjs.memory.buffer);
let buf = ""; let buf = "";
let nwritten = 0; let nwritten = 0;
@ -566,6 +747,7 @@ export const importObject = {
} }
memory.setUint32(nwritten_ptr, nwritten, true); memory.setUint32(nwritten_ptr, nwritten, true);
console.error(buf); console.error(buf);
}
}, },
fd_close: (...params) => { fd_close: (...params) => {
console.error("fd_close", params); console.error("fd_close", params);
@ -591,8 +773,38 @@ export const importObject = {
fd_pwrite: (...params) => { fd_pwrite: (...params) => {
console.error("fd_pwrite", params); console.error("fd_pwrite", params);
}, },
fd_read: (...params) => { fd_read(fd, iovs, iovsLen, nreadPtr) {
console.error("fd_read", params); if (fd >= fdStart) {
const memory = new DataView(zjs.memory.buffer);
let nwritten = 0;
for (let offset = iovs; offset < iovs + iovsLen * 8; offset += 8) {
const iov_base = memory.getUint32(offset, true);
const iov_len = memory.getUint32(offset + 4, true);
// new Uint8ClampedArray(memory.buffer.slice(iov_base, iov_base + iov_len)).fill(1);
nwritten += iov_len;
memory.setUint32(nreadPtr, nwritten, true);
}
} else {
const memory = new DataView(zjs.memory.buffer);
let nwritten = 0;
for (let offset = iovs; offset < iovs + iovsLen * 8; offset += 8) {
if (bytes == null) readStdin();
if (bytes == null) break;
const iov_base = memory.getUint32(offset, true);
const iov_len = memory.getUint32(offset + 4, true);
const read = Math.min(iov_len, bytes.length);
const io = new Uint8ClampedArray(zjs.memory.buffer, iov_base, iov_len);
io.set(bytes.slice(0, read));
bytes = bytes.slice(read);
if (bytes.length === 0) bytes = null;
nwritten += read;
if (read !== iov_len) break;
}
memory.setUint32(nreadPtr, nwritten, true);
if (nwritten > 0)
console.error("fd_read", nwritten);
}
}, },
fd_seek: (...params) => { fd_seek: (...params) => {
console.error("fd_seek", params); console.error("fd_seek", params);
@ -606,7 +818,207 @@ export const importObject = {
path_unlink_file: (...params) => { path_unlink_file: (...params) => {
console.error("path_unlink_file", params); console.error("path_unlink_file", params);
}, },
poll_oneoff: (...params) => { poll_oneoff(
sin: number,
sout: number,
nsubscriptions: number,
neventsPtr: number
) {
let nevents = 0;
let name = "";
// May have to wait this long (this gets computed below in the WASI_EVENTTYPE_CLOCK case).
let waitTimeNs = BigInt(0);
let fd = -1;
let fd_type: "read" | "write" = "read";
let fd_timeout_ms = 0;
const startNs = BigInt(msToNs(perfNow()));
let view = new DataView(zjs.memory.buffer);
let last_sin = sin;
for (let i = 0; i < nsubscriptions; i += 1) {
const userdata = view.getBigUint64(sin, true);
sin += 8;
const type = view.getUint8(sin);
sin += 1;
sin += 7; // padding
if (type == WASI_EVENTTYPE_CLOCK) {
name = "poll_oneoff (type=WASI_EVENTTYPE_CLOCK): ";
} else if (type == WASI_EVENTTYPE_FD_READ) {
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_READ): ";
} else {
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_WRITE): ";
}
console.log(name);
switch (type) {
case WASI_EVENTTYPE_CLOCK: {
// see packages/zig/dist/lib/libc/include/wasm-wasi-musl/wasi/api.h
// for exactly how these values are encoded. I carefully looked
// at that header and **this is definitely right**. Same with the fd
// in the other case below.
const clockid = view.getUint32(sin, true);
sin += 4;
sin += 4; // padding
let timeout = view.getBigUint64(sin, true);
console.log(timeout);
sin += 8;
// const precision = view.getBigUint64(sin, true);
sin += 8;
const subclockflags = view.getUint16(sin, true);
sin += 2;
sin += 6; // padding
const absolute = subclockflags === 1;
console.log(name, { clockid, timeout, absolute });
if (!absolute) {
fd_timeout_ms = Number(timeout / BigInt(1000000));
}
let e = WASI_ESUCCESS;
const t = now(clockid);
// logToFile(t, clockid, timeout, subclockflags, absolute);
if (t == null) {
e = WASI_EINVAL;
} else {
const end = absolute ? timeout : t + timeout;
const waitNs = end - t;
if (waitNs > waitTimeNs) {
waitTimeNs = waitNs;
}
}
view = new DataView(zjs.memory.buffer);
view.setBigUint64(sout, userdata, true);
sout += 8;
view.setUint16(sout, e, true); // error
sout += 8; // pad offset 2
view.setUint8(sout, WASI_EVENTTYPE_CLOCK);
sout += 8; // pad offset 1
sout += 8; // padding to 8
nevents += 1;
break;
}
case WASI_EVENTTYPE_FD_READ:
case WASI_EVENTTYPE_FD_WRITE: {
/*
Look at
lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c
to see how poll_oneoff is actually used by wasi to implement pselect.
It's also used in
lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/poll/poll.c
"If none of the selected descriptors are ready for the
requested operation, the pselect() or select() function shall
block until at least one of the requested operations becomes
ready, until the timeout occurs, or until interrupted by a signal."
Thus what is supposed to happen below is supposed
to block until the fd is ready to read from or write
to, etc.
For now at least if reading from stdin then we block for a short amount
of time if getStdin defined; otherwise, we at least *pause* for a moment
(to avoid cpu burn) if this.sleep is available.
*/
fd = view.getUint32(sin, true);
fd_type = type == WASI_EVENTTYPE_FD_READ ? "read" : "write";
sin += 4;
console.log(name, "fd =", fd);
sin += 28;
let notify = true;
if (fd >= fdStart) {
const has = new Int32Array(files.has);
Atomics.wait(has, fd - fdStart, 0, 500);
if (has[fd - fdStart] == 0) {
console.warn("not notify");
notify = false;
} else {
console.warn("notify");
notify = true;
Atomics.store(has, fd - fdStart, 0)
}
}
if (notify) {
view = new DataView(zjs.memory.buffer);
view.setBigUint64(sout, userdata, true);
sout += 8;
view.setUint16(sout, WASI_ENOSYS, true); // error
sout += 8; // pad offset 2
view.setUint8(sout, type);
sout += 8; // pad offset 3
sout += 8; // padding to 8
nevents += 1;
}
/*
TODO: for now for stdin we are just doing a dumb hack.
We just do something really naive, which is "pause for a little while".
It seems to work for every application I have so far, from Python to
to ncurses, etc. This also makes it easy to have non-blocking sleep
in node.js at the terminal without a worker thread, which is very nice!
Before I had it block here via getStdin when available, but that does not work
in general; in particular, it breaks ncurses completely. In
ncurses/tty/tty_update.c
the following call is assumed not to block, and if it does, then ncurses
interaction becomes totally broken:
select(SP_PARM->_checkfd + 1, &fdset, NULL, NULL, &ktimeout)
*/
if (fd == WASI_STDIN_FILENO && WASI_EVENTTYPE_FD_READ == type) {
sleep(5000);
}
break;
}
default:
return WASI_EINVAL;
}
// Consistency check that we consumed exactly the right amount
// of the __wasi_subscription_t. See zig/lib/libc/include/wasm-wasi-musl/wasi/api.h
if (sin - last_sin != 48) {
console.warn("*** BUG in wasi-js in poll_oneoff ", {
i,
sin,
last_sin,
diff: sin - last_sin,
});
}
last_sin = sin;
}
view = new DataView(zjs.memory.buffer);
view.setUint32(neventsPtr, nevents, true);
// if (nevents == 2 && fd >= 0) {
// const r = this.wasiImport.sock_pollSocket(fd, fd_type, fd_timeout_ms);
// if (r != WASI_ENOSYS) {
// // special implementation from outside
// return r;
// }
// // fall back to below
// }
// Account for the time it took to do everything above, which
// can be arbitrarily long:
if (waitTimeNs > 0) {
waitTimeNs -= msToNs(perfNow()) - startNs;
// logToFile("waitTimeNs", waitTimeNs);
if (waitTimeNs >= 1000000) {
const ms = nsToMs(waitTimeNs);
sleep(ms);
}
}
return WASI_ESUCCESS;
}, },
proc_exit: (...params) => { proc_exit: (...params) => {
console.error("proc_exit", params); console.error("proc_exit", params);
@ -632,7 +1044,7 @@ export const importObject = {
}, },
clock_time_get: (_clock, _precision, ptr) => { clock_time_get: (_clock, _precision, ptr) => {
const data = new DataView(zjs.memory.buffer); const data = new DataView(zjs.memory.buffer);
data.setBigUint64(ptr, BigInt(Date.now()) * 1000000n, true) data.setBigUint64(ptr, msToNs(perfNow()), true)
}, },
environ_sizes_get: (...params) => { environ_sizes_get: (...params) => {
console.error("environ_sizes_get", params); console.error("environ_sizes_get", params);
@ -640,11 +1052,22 @@ export const importObject = {
}, },
wasi: { wasi: {
"thread-spawn": (instance) => { "thread-spawn": (instance) => {
const worker = new Worker(new URL("worker.ts", import.meta.url), { type: "module" }); if (mainThread) {
worker.postMessage([zjs.memory, instance]); spawnWorker(instance)
} else {
postMessage([instance])
}
} }
}, },
...zjs.importObject(), ...zjs.importObject(),
}; };
function spawnWorker(instance) {
const worker = new Worker(new URL("worker.ts", import.meta.url), { type: "module" });
worker.postMessage([zjs.memory, instance, stdin, wasmModule, files]);
worker.onmessage = (event) => {
const [instance] = event.data;
spawnWorker(instance);
}
}

431
example/wasi.ts Normal file
View File

@ -0,0 +1,431 @@
/*
This project is based from the Node implementation made by Gus Caplan
https://github.com/devsnek/node-wasi
However, JavaScript WASI is focused on:
* Bringing WASI to the Browsers
* Make easy to plug different filesystems
* Provide a type-safe api using Typescript
Copyright 2019 Gus Caplan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
export const WASI_ESUCCESS = 0;
export const WASI_E2BIG = 1;
export const WASI_EACCES = 2;
export const WASI_EADDRINUSE = 3;
export const WASI_EADDRNOTAVAIL = 4;
export const WASI_EAFNOSUPPORT = 5;
export const WASI_EAGAIN = 6;
export const WASI_EALREADY = 7;
export const WASI_EBADF = 8;
export const WASI_EBADMSG = 9;
export const WASI_EBUSY = 10;
export const WASI_ECANCELED = 11;
export const WASI_ECHILD = 12;
export const WASI_ECONNABORTED = 13;
export const WASI_ECONNREFUSED = 14;
export const WASI_ECONNRESET = 15;
export const WASI_EDEADLK = 16;
export const WASI_EDESTADDRREQ = 17;
export const WASI_EDOM = 18;
export const WASI_EDQUOT = 19;
export const WASI_EEXIST = 20;
export const WASI_EFAULT = 21;
export const WASI_EFBIG = 22;
export const WASI_EHOSTUNREACH = 23;
export const WASI_EIDRM = 24;
export const WASI_EILSEQ = 25;
export const WASI_EINPROGRESS = 26;
export const WASI_EINTR = 27;
export const WASI_EINVAL = 28;
export const WASI_EIO = 29;
export const WASI_EISCONN = 30;
export const WASI_EISDIR = 31;
export const WASI_ELOOP = 32;
export const WASI_EMFILE = 33;
export const WASI_EMLINK = 34;
export const WASI_EMSGSIZE = 35;
export const WASI_EMULTIHOP = 36;
export const WASI_ENAMETOOLONG = 37;
export const WASI_ENETDOWN = 38;
export const WASI_ENETRESET = 39;
export const WASI_ENETUNREACH = 40;
export const WASI_ENFILE = 41;
export const WASI_ENOBUFS = 42;
export const WASI_ENODEV = 43;
export const WASI_ENOENT = 44;
export const WASI_ENOEXEC = 45;
export const WASI_ENOLCK = 46;
export const WASI_ENOLINK = 47;
export const WASI_ENOMEM = 48;
export const WASI_ENOMSG = 49;
export const WASI_ENOPROTOOPT = 50;
export const WASI_ENOSPC = 51;
export const WASI_ENOSYS = 52;
export const WASI_ENOTCONN = 53;
export const WASI_ENOTDIR = 54;
export const WASI_ENOTEMPTY = 55;
export const WASI_ENOTRECOVERABLE = 56;
export const WASI_ENOTSOCK = 57;
export const WASI_ENOTSUP = 58;
export const WASI_ENOTTY = 59;
export const WASI_ENXIO = 60;
export const WASI_EOVERFLOW = 61;
export const WASI_EOWNERDEAD = 62;
export const WASI_EPERM = 63;
export const WASI_EPIPE = 64;
export const WASI_EPROTO = 65;
export const WASI_EPROTONOSUPPORT = 66;
export const WASI_EPROTOTYPE = 67;
export const WASI_ERANGE = 68;
export const WASI_EROFS = 69;
export const WASI_ESPIPE = 70;
export const WASI_ESRCH = 71;
export const WASI_ESTALE = 72;
export const WASI_ETIMEDOUT = 73;
export const WASI_ETXTBSY = 74;
export const WASI_EXDEV = 75;
export const WASI_ENOTCAPABLE = 76;
export const WASI_SIGABRT = 0;
export const WASI_SIGALRM = 1;
export const WASI_SIGBUS = 2;
export const WASI_SIGCHLD = 3;
export const WASI_SIGCONT = 4;
export const WASI_SIGFPE = 5;
export const WASI_SIGHUP = 6;
export const WASI_SIGILL = 7;
export const WASI_SIGINT = 8;
export const WASI_SIGKILL = 9;
export const WASI_SIGPIPE = 10;
export const WASI_SIGQUIT = 11;
export const WASI_SIGSEGV = 12;
export const WASI_SIGSTOP = 13;
export const WASI_SIGTERM = 14;
export const WASI_SIGTRAP = 15;
export const WASI_SIGTSTP = 16;
export const WASI_SIGTTIN = 17;
export const WASI_SIGTTOU = 18;
export const WASI_SIGURG = 19;
export const WASI_SIGUSR1 = 20;
export const WASI_SIGUSR2 = 21;
export const WASI_SIGVTALRM = 22;
export const WASI_SIGXCPU = 23;
export const WASI_SIGXFSZ = 24;
export const WASI_FILETYPE_UNKNOWN = 0;
export const WASI_FILETYPE_BLOCK_DEVICE = 1;
export const WASI_FILETYPE_CHARACTER_DEVICE = 2;
export const WASI_FILETYPE_DIRECTORY = 3;
export const WASI_FILETYPE_REGULAR_FILE = 4;
export const WASI_FILETYPE_SOCKET_DGRAM = 5;
export const WASI_FILETYPE_SOCKET_STREAM = 6;
export const WASI_FILETYPE_SYMBOLIC_LINK = 7;
export type WASI_FILETYPE =
| typeof WASI_FILETYPE_UNKNOWN
| typeof WASI_FILETYPE_BLOCK_DEVICE
| typeof WASI_FILETYPE_CHARACTER_DEVICE
| typeof WASI_FILETYPE_DIRECTORY
| typeof WASI_FILETYPE_REGULAR_FILE
| typeof WASI_FILETYPE_SOCKET_DGRAM
| typeof WASI_FILETYPE_SOCKET_STREAM
| typeof WASI_FILETYPE_SYMBOLIC_LINK;
export const WASI_FDFLAG_APPEND = 0x0001;
export const WASI_FDFLAG_DSYNC = 0x0002;
export const WASI_FDFLAG_NONBLOCK = 0x0004;
export const WASI_FDFLAG_RSYNC = 0x0008;
export const WASI_FDFLAG_SYNC = 0x0010;
export const WASI_RIGHT_FD_DATASYNC = BigInt(0x0000000000000001);
export const WASI_RIGHT_FD_READ = BigInt(0x0000000000000002);
export const WASI_RIGHT_FD_SEEK = BigInt(0x0000000000000004);
export const WASI_RIGHT_FD_FDSTAT_SET_FLAGS = BigInt(0x0000000000000008);
export const WASI_RIGHT_FD_SYNC = BigInt(0x0000000000000010);
export const WASI_RIGHT_FD_TELL = BigInt(0x0000000000000020);
export const WASI_RIGHT_FD_WRITE = BigInt(0x0000000000000040);
export const WASI_RIGHT_FD_ADVISE = BigInt(0x0000000000000080);
export const WASI_RIGHT_FD_ALLOCATE = BigInt(0x0000000000000100);
export const WASI_RIGHT_PATH_CREATE_DIRECTORY = BigInt(0x0000000000000200);
export const WASI_RIGHT_PATH_CREATE_FILE = BigInt(0x0000000000000400);
export const WASI_RIGHT_PATH_LINK_SOURCE = BigInt(0x0000000000000800);
export const WASI_RIGHT_PATH_LINK_TARGET = BigInt(0x0000000000001000);
export const WASI_RIGHT_PATH_OPEN = BigInt(0x0000000000002000);
export const WASI_RIGHT_FD_READDIR = BigInt(0x0000000000004000);
export const WASI_RIGHT_PATH_READLINK = BigInt(0x0000000000008000);
export const WASI_RIGHT_PATH_RENAME_SOURCE = BigInt(0x0000000000010000);
export const WASI_RIGHT_PATH_RENAME_TARGET = BigInt(0x0000000000020000);
export const WASI_RIGHT_PATH_FILESTAT_GET = BigInt(0x0000000000040000);
export const WASI_RIGHT_PATH_FILESTAT_SET_SIZE = BigInt(0x0000000000080000);
export const WASI_RIGHT_PATH_FILESTAT_SET_TIMES = BigInt(0x0000000000100000);
export const WASI_RIGHT_FD_FILESTAT_GET = BigInt(0x0000000000200000);
export const WASI_RIGHT_FD_FILESTAT_SET_SIZE = BigInt(0x0000000000400000);
export const WASI_RIGHT_FD_FILESTAT_SET_TIMES = BigInt(0x0000000000800000);
export const WASI_RIGHT_PATH_SYMLINK = BigInt(0x0000000001000000);
export const WASI_RIGHT_PATH_REMOVE_DIRECTORY = BigInt(0x0000000002000000);
export const WASI_RIGHT_PATH_UNLINK_FILE = BigInt(0x0000000004000000);
export const WASI_RIGHT_POLL_FD_READWRITE = BigInt(0x0000000008000000);
export const WASI_RIGHT_SOCK_SHUTDOWN = BigInt(0x0000000010000000);
export const RIGHTS_ALL =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_SEEK |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_TELL |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_ALLOCATE |
WASI_RIGHT_PATH_CREATE_DIRECTORY |
WASI_RIGHT_PATH_CREATE_FILE |
WASI_RIGHT_PATH_LINK_SOURCE |
WASI_RIGHT_PATH_LINK_TARGET |
WASI_RIGHT_PATH_OPEN |
WASI_RIGHT_FD_READDIR |
WASI_RIGHT_PATH_READLINK |
WASI_RIGHT_PATH_RENAME_SOURCE |
WASI_RIGHT_PATH_RENAME_TARGET |
WASI_RIGHT_PATH_FILESTAT_GET |
WASI_RIGHT_PATH_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_SYMLINK |
WASI_RIGHT_PATH_UNLINK_FILE |
WASI_RIGHT_PATH_REMOVE_DIRECTORY |
WASI_RIGHT_POLL_FD_READWRITE |
WASI_RIGHT_SOCK_SHUTDOWN;
export const RIGHTS_BLOCK_DEVICE_BASE = RIGHTS_ALL;
export const RIGHTS_BLOCK_DEVICE_INHERITING = RIGHTS_ALL;
export const RIGHTS_CHARACTER_DEVICE_BASE = RIGHTS_ALL;
export const RIGHTS_CHARACTER_DEVICE_INHERITING = RIGHTS_ALL;
export const RIGHTS_REGULAR_FILE_BASE =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_SEEK |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_TELL |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_ALLOCATE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_SIZE |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_REGULAR_FILE_INHERITING = BigInt(0);
export const RIGHTS_DIRECTORY_BASE =
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_PATH_CREATE_DIRECTORY |
WASI_RIGHT_PATH_CREATE_FILE |
WASI_RIGHT_PATH_LINK_SOURCE |
WASI_RIGHT_PATH_LINK_TARGET |
WASI_RIGHT_PATH_OPEN |
WASI_RIGHT_FD_READDIR |
WASI_RIGHT_PATH_READLINK |
WASI_RIGHT_PATH_RENAME_SOURCE |
WASI_RIGHT_PATH_RENAME_TARGET |
WASI_RIGHT_PATH_FILESTAT_GET |
WASI_RIGHT_PATH_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_PATH_SYMLINK |
WASI_RIGHT_PATH_UNLINK_FILE |
WASI_RIGHT_PATH_REMOVE_DIRECTORY |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_DIRECTORY_INHERITING =
RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE;
export const RIGHTS_SOCKET_BASE =
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE |
WASI_RIGHT_SOCK_SHUTDOWN;
export const RIGHTS_SOCKET_INHERITING = RIGHTS_ALL;
export const RIGHTS_TTY_BASE =
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_TTY_INHERITING = BigInt(0);
export const WASI_CLOCK_REALTIME = 0;
export const WASI_CLOCK_MONOTONIC = 1;
export const WASI_CLOCK_PROCESS_CPUTIME_ID = 2;
export const WASI_CLOCK_THREAD_CPUTIME_ID = 3;
export const WASI_EVENTTYPE_CLOCK = 0;
export const WASI_EVENTTYPE_FD_READ = 1;
export const WASI_EVENTTYPE_FD_WRITE = 2;
export const WASI_FILESTAT_SET_ATIM = 1 << 0;
export const WASI_FILESTAT_SET_ATIM_NOW = 1 << 1;
export const WASI_FILESTAT_SET_MTIM = 1 << 2;
export const WASI_FILESTAT_SET_MTIM_NOW = 1 << 3;
export const WASI_O_CREAT = 1 << 0;
export const WASI_O_DIRECTORY = 1 << 1;
export const WASI_O_EXCL = 1 << 2;
export const WASI_O_TRUNC = 1 << 3;
export const WASI_PREOPENTYPE_DIR = 0;
export const WASI_DIRCOOKIE_START = 0;
export const WASI_STDIN_FILENO = 0;
export const WASI_STDOUT_FILENO = 1;
export const WASI_STDERR_FILENO = 2;
export const WASI_WHENCE_SET = 0;
export const WASI_WHENCE_CUR = 1;
export const WASI_WHENCE_END = 2;
// http://man7.org/linux/man-pages/man3/errno.3.html
export const ERROR_MAP: { [key: string]: number } = {
E2BIG: WASI_E2BIG,
EACCES: WASI_EACCES,
EADDRINUSE: WASI_EADDRINUSE,
EADDRNOTAVAIL: WASI_EADDRNOTAVAIL,
EAFNOSUPPORT: WASI_EAFNOSUPPORT,
EALREADY: WASI_EALREADY,
EAGAIN: WASI_EAGAIN,
// EBADE: WASI_EBADE,
EBADF: WASI_EBADF,
// EBADFD: WASI_EBADFD,
EBADMSG: WASI_EBADMSG,
// EBADR: WASI_EBADR,
// EBADRQC: WASI_EBADRQC,
// EBADSLT: WASI_EBADSLT,
EBUSY: WASI_EBUSY,
ECANCELED: WASI_ECANCELED,
ECHILD: WASI_ECHILD,
// ECHRNG: WASI_ECHRNG,
// ECOMM: WASI_ECOMM,
ECONNABORTED: WASI_ECONNABORTED,
ECONNREFUSED: WASI_ECONNREFUSED,
ECONNRESET: WASI_ECONNRESET,
EDEADLOCK: WASI_EDEADLK,
EDESTADDRREQ: WASI_EDESTADDRREQ,
EDOM: WASI_EDOM,
EDQUOT: WASI_EDQUOT,
EEXIST: WASI_EEXIST,
EFAULT: WASI_EFAULT,
EFBIG: WASI_EFBIG,
EHOSTDOWN: WASI_EHOSTUNREACH,
EHOSTUNREACH: WASI_EHOSTUNREACH,
// EHWPOISON: WASI_EHWPOISON,
EIDRM: WASI_EIDRM,
EILSEQ: WASI_EILSEQ,
EINPROGRESS: WASI_EINPROGRESS,
EINTR: WASI_EINTR,
EINVAL: WASI_EINVAL,
EIO: WASI_EIO,
EISCONN: WASI_EISCONN,
EISDIR: WASI_EISDIR,
ELOOP: WASI_ELOOP,
EMFILE: WASI_EMFILE,
EMLINK: WASI_EMLINK,
EMSGSIZE: WASI_EMSGSIZE,
EMULTIHOP: WASI_EMULTIHOP,
ENAMETOOLONG: WASI_ENAMETOOLONG,
ENETDOWN: WASI_ENETDOWN,
ENETRESET: WASI_ENETRESET,
ENETUNREACH: WASI_ENETUNREACH,
ENFILE: WASI_ENFILE,
ENOBUFS: WASI_ENOBUFS,
ENODEV: WASI_ENODEV,
ENOENT: WASI_ENOENT,
ENOEXEC: WASI_ENOEXEC,
ENOLCK: WASI_ENOLCK,
ENOLINK: WASI_ENOLINK,
ENOMEM: WASI_ENOMEM,
ENOMSG: WASI_ENOMSG,
ENOPROTOOPT: WASI_ENOPROTOOPT,
ENOSPC: WASI_ENOSPC,
ENOSYS: WASI_ENOSYS,
ENOTCONN: WASI_ENOTCONN,
ENOTDIR: WASI_ENOTDIR,
ENOTEMPTY: WASI_ENOTEMPTY,
ENOTRECOVERABLE: WASI_ENOTRECOVERABLE,
ENOTSOCK: WASI_ENOTSOCK,
ENOTTY: WASI_ENOTTY,
ENXIO: WASI_ENXIO,
EOVERFLOW: WASI_EOVERFLOW,
EOWNERDEAD: WASI_EOWNERDEAD,
EPERM: WASI_EPERM,
EPIPE: WASI_EPIPE,
EPROTO: WASI_EPROTO,
EPROTONOSUPPORT: WASI_EPROTONOSUPPORT,
EPROTOTYPE: WASI_EPROTOTYPE,
ERANGE: WASI_ERANGE,
EROFS: WASI_EROFS,
ESPIPE: WASI_ESPIPE,
ESRCH: WASI_ESRCH,
ESTALE: WASI_ESTALE,
ETIMEDOUT: WASI_ETIMEDOUT,
ETXTBSY: WASI_ETXTBSY,
EXDEV: WASI_EXDEV
};
export const SIGNAL_MAP: { [key: string]: string } = {
[WASI_SIGHUP]: "SIGHUP",
[WASI_SIGINT]: "SIGINT",
[WASI_SIGQUIT]: "SIGQUIT",
[WASI_SIGILL]: "SIGILL",
[WASI_SIGTRAP]: "SIGTRAP",
[WASI_SIGABRT]: "SIGABRT",
[WASI_SIGBUS]: "SIGBUS",
[WASI_SIGFPE]: "SIGFPE",
[WASI_SIGKILL]: "SIGKILL",
[WASI_SIGUSR1]: "SIGUSR1",
[WASI_SIGSEGV]: "SIGSEGV",
[WASI_SIGUSR2]: "SIGUSR2",
[WASI_SIGPIPE]: "SIGPIPE",
[WASI_SIGALRM]: "SIGALRM",
[WASI_SIGTERM]: "SIGTERM",
[WASI_SIGCHLD]: "SIGCHLD",
[WASI_SIGCONT]: "SIGCONT",
[WASI_SIGSTOP]: "SIGSTOP",
[WASI_SIGTSTP]: "SIGTSTP",
[WASI_SIGTTIN]: "SIGTTIN",
[WASI_SIGTTOU]: "SIGTTOU",
[WASI_SIGURG]: "SIGURG",
[WASI_SIGXCPU]: "SIGXCPU",
[WASI_SIGXFSZ]: "SIGXFSZ",
[WASI_SIGVTALRM]: "SIGVTALRM"
};

View File

@ -1,11 +1,14 @@
import { importObject, zjs } from "./imports"; import { importObject, setFiles, setMainThread, setStdin, zjs } from "./imports";
onmessage = async (e) => { onmessage = async (e) => {
console.log("module received from main thread"); console.log("module received from main thread");
const [memory, instance] = e.data; const [memory, instance, stdin, wasmModule, files] = e.data;
console.log(wasmModule)
setStdin(stdin);
setMainThread(false);
setFiles(files);
importObject.env.memory = memory; importObject.env.memory = memory;
const url = new URL("ghostty-wasm.wasm", import.meta.url); const results = await WebAssembly.instantiate(wasmModule, importObject);
const results = await WebAssembly.instantiateStreaming(fetch(url), importObject)
zjs.memory = memory; zjs.memory = memory;
results.instance.exports.wasi_thread_start(100, instance); results.exports.wasi_thread_start(100, instance);
}; };

View File

@ -203,48 +203,9 @@ fn startWasi(self: *Command, arena: Allocator) !void {
else else
@compileError("missing env vars"); @compileError("missing env vars");
// Fork. If we have a cgroup specified on Linxu then we use clone self.pid = 100;
const pid: posix.pid_t = switch (builtin.os.tag) {
.linux => if (self.linux_cgroup) |cgroup|
try internal_os.cgroup.cloneInto(cgroup)
else
try posix.fork(),
else => try posix.fork(), std.log.err("need to fork {s} {*}", .{ pathZ, envp });
};
if (pid != 0) {
// Parent, return immediately.
self.pid = @intCast(pid);
return;
}
// We are the child.
// Setup our file descriptors for std streams.
if (self.stdin) |f| setupFd(f.handle, posix.STDIN_FILENO) catch
return error.ExecFailedInChild;
if (self.stdout) |f| setupFd(f.handle, posix.STDOUT_FILENO) catch
return error.ExecFailedInChild;
if (self.stderr) |f| setupFd(f.handle, posix.STDERR_FILENO) catch
return error.ExecFailedInChild;
// Setup our working directory
if (self.cwd) |cwd| posix.chdir(cwd) catch {
// This can fail if we don't have permission to go to
// this directory or if due to race conditions it doesn't
// exist or any various other reasons. We don't want to
// crash the entire process if this fails so we ignore it.
// We don't log because that'll show up in the output.
};
// If the user requested a pre exec callback, call it now.
if (self.pre_exec) |f| f(self);
std.log.err("{s} {*}", .{ pathZ, envp });
// If we are executing this code, the exec failed. In that scenario,
// we return a very specific error that can be detected to determine
// we're in the child.
return; return;
} }

View File

@ -29,9 +29,6 @@ pub const Face = struct {
/// Metrics for this font face. These are useful for renderers. /// Metrics for this font face. These are useful for renderers.
metrics: font.face.Metrics, metrics: font.face.Metrics,
/// The canvas element that we will reuse to render glyphs
canvas: js.Object,
/// The map to store multi-codepoint grapheme clusters that are rendered. /// The map to store multi-codepoint grapheme clusters that are rendered.
/// We use 1 above the maximum unicode codepoint up to the max 32-bit /// We use 1 above the maximum unicode codepoint up to the max 32-bit
/// unsigned integer to store the "glyph index" for graphemes. /// unsigned integer to store the "glyph index" for graphemes.
@ -58,20 +55,12 @@ pub const Face = struct {
const font_str = try alloc.dupe(u8, raw); const font_str = try alloc.dupe(u8, raw);
errdefer alloc.free(font_str); errdefer alloc.free(font_str);
// Create our canvasxx that we're going to continue to reuse.
const OffscreenCanvas = try js.global.get(js.Object, "OffscreenCanvas");
defer OffscreenCanvas.deinit();
const canvas = try OffscreenCanvas.new(.{ 0, 0 });
errdefer canvas.deinit();
var result = Face{ var result = Face{
.alloc = alloc, .alloc = alloc,
.font_str = font_str, .font_str = font_str,
.size = size, .size = size,
.presentation = presentation, .presentation = presentation,
.canvas = canvas,
// We're going to calculate these right after initialization. // We're going to calculate these right after initialization.
.metrics = undefined, .metrics = undefined,
}; };
@ -89,7 +78,6 @@ pub const Face = struct {
while (it.next()) |value| self.alloc.free(value.*); while (it.next()) |value| self.alloc.free(value.*);
self.glyph_to_grapheme.deinit(self.alloc); self.glyph_to_grapheme.deinit(self.alloc);
} }
self.canvas.deinit();
self.* = undefined; self.* = undefined;
} }
@ -291,13 +279,15 @@ pub const Face = struct {
fn context(self: Face) !js.Object { fn context(self: Face) !js.Object {
// This will return the same context on subsequent calls so it // This will return the same context on subsequent calls so it
// is important to reset it. // is important to reset it.
const ctx = try self.canvas.call(js.Object, "getContext", .{js.string("2d")}); const canvas = try js.global.get(js.Object, "fontCanvas");
defer canvas.deinit();
const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")});
errdefer ctx.deinit(); errdefer ctx.deinit();
// Clear the canvas // Clear the canvas
{ {
const width = try self.canvas.get(f64, "width"); const width = try canvas.get(f64, "width");
const height = try self.canvas.get(f64, "height"); const height = try canvas.get(f64, "height");
try ctx.call(void, "clearRect", .{ 0, 0, width, height }); try ctx.call(void, "clearRect", .{ 0, 0, width, height });
} }
@ -428,8 +418,10 @@ pub const Face = struct {
// Resize canvas to match the glyph size exactly // Resize canvas to match the glyph size exactly
{ {
try self.canvas.set("width", width); const canvas = try js.global.get(js.Object, "fontCanvas");
try self.canvas.set("height", height); defer canvas.deinit();
try canvas.set("width", width);
try canvas.set("height", height);
// const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width}); // const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width});
// defer alloc.free(width_str); // defer alloc.free(width_str);
@ -562,25 +554,25 @@ pub const Wasm = struct {
}; };
} }
export fn face_debug_canvas(face: *Face) void { // export fn face_debug_canvas(face: *Face) void {
face_debug_canvas_(face) catch |err| { // face_debug_canvas_(face) catch |err| {
log.warn("error adding debug canvas err={}", .{err}); // log.warn("error adding debug canvas err={}", .{err});
}; // };
} // }
fn face_debug_canvas_(face: *Face) !void { // fn face_debug_canvas_(face: *Face) !void {
const doc = try js.global.get(js.Object, "document"); // const doc = try js.global.get(js.Object, "document");
defer doc.deinit(); // defer doc.deinit();
const elem = try doc.call( // const elem = try doc.call(
?js.Object, // ?js.Object,
"getElementById", // "getElementById",
.{js.string("face-canvas")}, // .{js.string("face-canvas")},
) orelse return error.CanvasContainerNotFound; // ) orelse return error.CanvasContainerNotFound;
defer elem.deinit(); // defer elem.deinit();
try elem.call(void, "append", .{face.canvas}); // try elem.call(void, "append", .{face.canvas});
} // }
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph { fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{}); const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{});

View File

@ -26,6 +26,14 @@ export fn run(str: [*]const u8, len: usize) void {
std.log.err("err: {?}", .{err}); std.log.err("err: {?}", .{err});
}; };
} }
var surf: ?*Surface = null;
export fn draw() void {
surf.?.renderer.drawFrame(surf.?.rt_surface) catch |err| {
std.log.err("err: {?}", .{err});
};
}
fn run_(str: []const u8) !void { fn run_(str: []const u8) !void {
var config = try Config.default(alloc); var config = try Config.default(alloc);
var fbs = std.io.fixedBufferStream(str); var fbs = std.io.fixedBufferStream(str);
@ -41,28 +49,29 @@ fn run_(str: []const u8) !void {
try surface.init(alloc, &config, app, &app_runtime, apprt_surface); try surface.init(alloc, &config, app, &app_runtime, apprt_surface);
std.log.err("{}", .{surface.size}); std.log.err("{}", .{surface.size});
try surface.renderer.setScreenSize(surface.size); try surface.renderer.setScreenSize(surface.size);
const esc = "\x1b["; surf = surface;
surface.io.processOutput("M_yhelloaaaaaaaaa\n\r🐏\n\r👍🏽\n\rM_ghostty" ++ esc ++ "2;2H" ++ esc ++ "48;2;240;40;40m" ++ esc ++ "38;2;23;255;80mhello"); // const esc = "\x1b[";
// try surface.renderer_state.terminal.printString("M_yhelloaaaaaaaaa\n🐏\n👍🏽\nM_ghostty"); // surface.io.processOutput("M_yhelloaaaaaaaaa\n\r🐏\n\r👍🏽\n\rM_ghostty" ++ esc ++ "2;2H" ++ esc ++ "48;2;240;40;40m" ++ esc ++ "38;2;23;255;80mhello");
// surface.renderer_state.terminal.setCursorPos(4, 2); // // try surface.renderer_state.terminal.printString("M_yhelloaaaaaaaaa\n🐏\n👍🏽\nM_ghostty");
// try surface.renderer_state.terminal.setAttribute(.{ .direct_color_bg = .{ // // surface.renderer_state.terminal.setCursorPos(4, 2);
// .r = 240, // // try surface.renderer_state.terminal.setAttribute(.{ .direct_color_bg = .{
// .g = 40, // // .r = 240,
// .b = 40, // // .g = 40,
// } }); // // .b = 40,
// try surface.renderer_state.terminal.setAttribute(.{ .direct_color_fg = .{ // // } });
// .r = 255, // // try surface.renderer_state.terminal.setAttribute(.{ .direct_color_fg = .{
// .g = 255, // // .r = 255,
// .b = 255, // // .g = 255,
// } }); // // .b = 255,
// try surface.renderer_state.terminal.printString("hello"); // // } });
try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false); // // try surface.renderer_state.terminal.printString("hello");
try surface.renderer.drawFrame(apprt_surface); // try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false);
try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false); // try surface.renderer.drawFrame(apprt_surface);
try surface.renderer.drawFrame(apprt_surface); // try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false);
// try surface.renderer.drawFrame(apprt_surface);
// const webgl = try renderer.OpenGL.init(alloc, .{ .config = try renderer.OpenGL.DerivedConfig.init(alloc, &config) }); // // const webgl = try renderer.OpenGL.init(alloc, .{ .config = try renderer.OpenGL.DerivedConfig.init(alloc, &config) });
// _ = webgl; // // _ = webgl;
} }
pub const std_options: std.Options = .{ pub const std_options: std.Options = .{

View File

@ -683,6 +683,7 @@ pub fn updateFrame(
) !void { ) !void {
_ = surface; _ = surface;
std.log.err("update frame", .{});
// Data we extract out of the critical area. // Data we extract out of the critical area.
const Critical = struct { const Critical = struct {
full_rebuild: bool, full_rebuild: bool,
@ -701,6 +702,7 @@ pub fn updateFrame(
state.mutex.lock(); state.mutex.lock();
defer state.mutex.unlock(); defer state.mutex.unlock();
std.log.err("critical", .{});
// If we're in a synchronized output state, we pause all rendering. // If we're in a synchronized output state, we pause all rendering.
if (state.terminal.modes.get(.synchronized_output)) { if (state.terminal.modes.get(.synchronized_output)) {
@ -2229,8 +2231,9 @@ fn flushAtlasSingle(
/// the cells. /// the cells.
pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void { pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
// If we're in single-threaded more we grab a lock since we use shared data. // If we're in single-threaded more we grab a lock since we use shared data.
if (single_threaded_draw) self.draw_mutex.lock(); // wasm can't use a mutex on the main thread because it uses Atomic.wait
defer if (single_threaded_draw) self.draw_mutex.unlock(); if (single_threaded_draw and builtin.cpu.arch != .wasm32) self.draw_mutex.lock();
defer if (single_threaded_draw and builtin.cpu.arch != .wasm32) self.draw_mutex.unlock();
const gl_state: *GLState = if (self.gl_state) |*v| v else return; const gl_state: *GLState = if (self.gl_state) |*v| v else return;
// Go through our images and see if we need to setup any textures. // Go through our images and see if we need to setup any textures.

View File

@ -232,7 +232,7 @@ fn threadMain_(self: *Thread) !void {
self.startDrawTimer(); self.startDrawTimer();
// Run // Run
log.debug("starting renderer thread", .{}); log.err("starting renderer thread", .{});
defer log.debug("starting renderer thread shutdown", .{}); defer log.debug("starting renderer thread shutdown", .{});
_ = try self.loop.run(.until_done); _ = try self.loop.run(.until_done);
} }
@ -437,6 +437,7 @@ fn wakeupCallback(
}; };
const t = self_.?; const t = self_.?;
log.err("wakeup", .{});
// When we wake up, we check the mailbox. Mailbox producers should // When we wake up, we check the mailbox. Mailbox producers should
// wake up our thread after publishing. // wake up our thread after publishing.

View File

@ -126,7 +126,7 @@ pub fn threadEnter(
// Start our read thread // Start our read thread
const read_thread = try std.Thread.spawn( const read_thread = try std.Thread.spawn(
.{}, .{ .allocator = alloc },
if (builtin.os.tag == .windows) ReadThread.threadMainWindows else ReadThread.threadMainPosix, if (builtin.os.tag == .windows) ReadThread.threadMainWindows else ReadThread.threadMainPosix,
.{ pty_fds.read, io, pipe[0] }, .{ pty_fds.read, io, pipe[0] },
); );
@ -1424,6 +1424,7 @@ pub const ReadThread = struct {
// child process dies. To be safe, we just break the loop // child process dies. To be safe, we just break the loop
// and let our poll happen. // and let our poll happen.
if (n == 0) break; if (n == 0) break;
std.log.err("{} {} {}", .{ buf[0], n, @intFromPtr(&buf) });
// log.info("DATA: {d}", .{n}); // log.info("DATA: {d}", .{n});
@call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] }); @call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] });

View File

@ -554,6 +554,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// use a timer under the covers // use a timer under the covers
if (internal_os.Instant.now()) |now| cursor_reset: { if (internal_os.Instant.now()) |now| cursor_reset: {
if (self.last_cursor_reset) |last| { if (self.last_cursor_reset) |last| {
log.err("now: {} last: {}", .{ now, last });
if (now.since(last) <= (500 * std.time.ns_per_ms)) { if (now.since(last) <= (500 * std.time.ns_per_ms)) {
break :cursor_reset; break :cursor_reset;
} }
@ -571,6 +572,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// process a byte at a time alternating between the inspector handler // process a byte at a time alternating between the inspector handler
// and the termio handler. This is very slow compared to our optimizations // and the termio handler. This is very slow compared to our optimizations
// below but at least users only pay for it if they're using the inspector. // below but at least users only pay for it if they're using the inspector.
std.log.err("to print {s}", .{buf});
if (builtin.cpu.arch == .wasm32) { if (builtin.cpu.arch == .wasm32) {
self.terminal_stream.nextSlice(buf) catch |err| self.terminal_stream.nextSlice(buf) catch |err|
log.err("error processing terminal data: {}", .{err}); log.err("error processing terminal data: {}", .{err});