vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 40937aa3f7a2b6f9175480bdb5df5541fc13bf5b
Parent: 7f3c9e87be9ee8366bddcb10efa7cb6119a3bbd8
Author: Randy Palamar
Date:   Tue, 29 Oct 2024 06:42:56 -0600

remove stdio usage

Diffstat:
Mdebug.c | 80+++++++++++++++++++++++++++++++++++++------------------------------------------
Mdebug.h | 1+
Mmain.c | 114++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mos_unix.c | 49++++++++++++++++++++++++++++++++++++++++++++++---
Mterminal.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mtest.c | 23+++++++++++++++++++----
Mutil.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 18++++++++++++++++++
Mvtgl.c | 48++++++++++++++++++++++++++++++------------------
9 files changed, 459 insertions(+), 164 deletions(-)

diff --git a/debug.c b/debug.c @@ -33,63 +33,57 @@ simulate_line(Term *t, Line *line) } static void -fput_cursor_info(FILE *f, Cursor c) -{ - fprintf(f, "\tFG: 0x%08x\n", c.style.fg.rgba); - fprintf(f, "\tBG: 0x%08x\n", c.style.bg.rgba); - fprintf(f, "\tAttr: 0x%08x\n", c.style.attr); - fprintf(f, "\tPos: {%d, %d}\n", c.pos.y, c.pos.x); -} - -static void -fput_line_info(FILE *f, Term *t, Line *l) -{ - Cursor start = t->cursor; - start.style = l->cursor_state; - fprintf(f, "Line Info:\n"); - fprintf(f, "\tLength: %ld\n", line_length(l)); - fprintf(f, "\tHas Unicode: %d\n", l->has_unicode); - fput_cursor_info(f, start); - Cursor end = simulate_line(t, l); - fprintf(f, "After Line Cursor State:\n"); - fput_cursor_info(f, end); -} - -static void dump_lines_to_file(Term *t) { - char buf[256]; + u8 buf[256]; + Stream fname = {.cap = sizeof(buf), .buf = buf}; u64 current_time = os_get_time(); - snprintf(buf, sizeof(buf), "%zu-lines.bin", current_time); + stream_push_u64(&fname, current_time); + stream_push_s8(&fname, s8("-lines.bin\0")); - FILE *f = fopen(buf, "w"); - if (!f) return; + iptr file = os_open(buf, FA_WRITE); + if (file == INVALID_FILE) return; - printf("dumping lines to %s\n", buf); + fname.buf[fname.widx - 1] = '\n'; + os_write_err_msg(s8("dumping lines to ")); + os_write_err_msg(stream_to_s8(&fname)); TermView *tv = t->views + t->view_idx; size line_count = MIN(256, tv->lines.filled); - fputs("Term Info:\n", f); - fprintf(f, " Size: %ux%u\n", t->size.w, t->size.h); - fprintf(f, " Window Size: %ux%u\n", (u32)t->gl.window_size.w, (u32)t->gl.window_size.h); - fprintf(f, " Mode: 0x%X\n", t->mode); - fprintf(f, " Window Mode: 0x%X\n", t->gl.mode); - fprintf(f, " Scroll Region:\n"); - fprintf(f, " Top: %u\n", t->top); - fprintf(f, " Bottom: %u\n", t->bot); - fprintf(f, " Offset: %d\n", t->scroll_offset); - fprintf(f, "Raw Line Count: %u\n", (u32)line_count); - fputs("==============================\n", f); - + Arena temp_arena = t->arena_for_frame; + Stream out = stream_alloc(&temp_arena, 1 * MEGABYTE); + + iv2 tsize = {.w = t->size.w, .h = t->size.h}; + iv2 wsize = {.w = t->gl.window_size.w, .h = t->gl.window_size.h}; + stream_push_s8(&out, s8("Term Info:")); + stream_push_s8(&out, s8("\n Size: ")); stream_push_iv2(&out, tsize); + stream_push_s8(&out, s8("\n Window Size: ")); stream_push_iv2(&out, wsize); + stream_push_s8(&out, s8("\n Mode: 0x")); stream_push_hex_u64(&out, t->mode); + stream_push_s8(&out, s8("\n Window Mode: 0x")); stream_push_hex_u64(&out, t->gl.mode); + stream_push_s8(&out, s8("\n Scroll Region:")); + stream_push_s8(&out, s8("\n Top: ")); stream_push_u64(&out, t->top); + stream_push_s8(&out, s8("\n Bottom: ")); stream_push_u64(&out, t->bot); + stream_push_s8(&out, s8("\n Offset: ")); stream_push_i64(&out, t->scroll_offset); + stream_push_s8(&out, s8("\nRaw Line Count: ")); stream_push_u64(&out, (u32)line_count); + stream_push_s8(&out, s8("\n==============================\n")); + + size file_offset = 0; for (size i = -(line_count - 1); i <= 0; i++) { Line *line = tv->lines.buf + get_line_idx(&tv->lines, i); s8 l = line_to_s8(line, &tv->log); - fwrite(l.data, 1, l.len, f); + stream_push_s8(&out, l); + if (out.errors) { + os_write(file, stream_to_s8(&out), file_offset); + file_offset += out.widx; + out.widx = 0; + stream_push_s8(&out, l); + } } - fputc('\n', f); + stream_push_byte(&out, '\n'); - fclose(f); + os_write(file, stream_to_s8(&out), file_offset); + os_close(file); } static void diff --git a/debug.h b/debug.h @@ -11,6 +11,7 @@ #else #define ASSERT(c) do { if (!(c)) asm("int3; nop"); } while(0) +#define INVALID_CODE_PATH ASSERT(0) #define DEBUG_EXPORT typedef struct { diff --git a/main.c b/main.c @@ -28,27 +28,28 @@ typedef iv2 init_term_fn(Term *, Arena *, iv2); static init_term_fn *init_term; static void -load_library(const char *lib) +load_library(const char *lib, Stream *err) { + s8 nl = s8("\n"); /* NOTE: glibc sucks and will crash if this is NULL */ if (libhandle) dlclose(libhandle); libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL); if (!libhandle) - fprintf(stderr, "do_debug: dlopen: %s\n", dlerror()); + stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl}); do_terminal = dlsym(libhandle, "do_terminal"); if (!do_terminal) - fprintf(stderr, "do_debug: dlsym: %s\n", dlerror()); + stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl}); init_callbacks = dlsym(libhandle, "init_callbacks"); if (!init_callbacks) - fprintf(stderr, "do_debug: dlsym: %s\n", dlerror()); + stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl}); init_term = dlsym(libhandle, "init_term"); if (!init_term) - fprintf(stderr, "do_debug: dlsym: %s\n", dlerror()); + stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl}); } static void -do_debug(GLCtx *gl) +do_debug(GLCtx *gl, Stream *err) { static os_file_stats updated; os_file_stats test = os_get_file_stats(libname); @@ -58,11 +59,16 @@ do_debug(GLCtx *gl) /* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */ struct timespec sleep_time = { .tv_nsec = 100e6 }; nanosleep(&sleep_time, &sleep_time); - load_library(libname); + load_library(libname, err); if (gl) { init_callbacks(gl); } - os_write_err_msg(s8("Reloaded Main Program\n")); + stream_push_s8(err, s8("Reloaded Main Program\n")); + } + + if (err->widx) { + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; } } @@ -71,24 +77,34 @@ do_debug(GLCtx *gl) static void gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) { - (void)src; (void)type; (void)id; (void)data; - os_write_err_msg(s8("[GL Error ")); + (void)src; (void)type; (void)id; + Stream *err = (Stream *)data; + stream_push_s8(err, s8("[GL Error ")); switch (lvl) { - case GL_DEBUG_SEVERITY_HIGH: os_write_err_msg(s8("HIGH]: ")); break; - case GL_DEBUG_SEVERITY_MEDIUM: os_write_err_msg(s8("MEDIUM]: ")); break; - case GL_DEBUG_SEVERITY_LOW: os_write_err_msg(s8("LOW]: ")); break; - case GL_DEBUG_SEVERITY_NOTIFICATION: os_write_err_msg(s8("NOTIFICATION]: ")); break; - default: os_write_err_msg(s8("INVALID]: ")); break; + case GL_DEBUG_SEVERITY_HIGH: stream_push_s8(err, s8("HIGH]: ")); break; + case GL_DEBUG_SEVERITY_MEDIUM: stream_push_s8(err, s8("MEDIUM]: ")); break; + case GL_DEBUG_SEVERITY_LOW: stream_push_s8(err, s8("LOW]: ")); break; + case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break; + default: stream_push_s8(err, s8("INVALID]: ")); break; } - os_write_err_msg((s8){.len = len, .data = (u8 *)msg}); - os_write_err_msg(s8("\n")); + stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); + stream_push_byte(err, '\n'); + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; } static void error_callback(int code, const char *desc) { - fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc); + u8 buf[256]; + Stream err = {.cap = sizeof(buf), .buf = buf}; + stream_push_s8(&err, s8("GLFW Error (0x")); + stream_push_hex_u64(&err, code); + stream_push_s8(&err, s8("): ")); + os_write_err_msg(stream_to_s8(&err)); + os_write_err_msg(c_str_to_s8((char *)desc)); + os_write_err_msg(s8("\n")); } static u32 @@ -262,7 +278,7 @@ program_from_shader_text(s8 vertex, s8 fragment, Arena a) } static void -check_shaders(GLCtx *gl, Arena a) +check_shaders(GLCtx *gl, Arena a, Stream *err) { static char *fs_name[SHADER_LAST] = { "frag_render.glsl", @@ -277,12 +293,12 @@ check_shaders(GLCtx *gl, Arena a) static os_file_stats fs_stats[SHADER_LAST], vs_stats[SHADER_LAST]; static struct { - char *name; - u32 flag; + s8 name; + u32 flag; } map[SHADER_LAST] = { - [SHADER_RENDER] = {.name = "Render", .flag = 0}, - [SHADER_RECTS] = {.name = "Rects", .flag = 0}, - [SHADER_POST] = {.name = "Post", .flag = UPDATE_POST_UNIFORMS}, + [SHADER_RENDER] = {.name = s8("Render"), .flag = 0}, + [SHADER_RECTS] = {.name = s8("Rects"), .flag = 0}, + [SHADER_POST] = {.name = s8("Post"), .flag = UPDATE_POST_UNIFORMS}, }; for (u32 i = 0; i < SHADER_LAST; i++) { @@ -293,7 +309,7 @@ check_shaders(GLCtx *gl, Arena a) !os_filetime_changed(vs_test.timestamp, vs_stats[i].timestamp)) continue; - fprintf(stderr, "Reloading %s Shader!\n", map[i].name); + stream_push_s8s(err, 3, (s8 []){s8("Reloading "), map[i].name, s8(" Shader!\n")}); fs_stats[i] = fs_test; vs_stats[i] = vs_test; @@ -307,24 +323,32 @@ check_shaders(GLCtx *gl, Arena a) glDeleteProgram(gl->programs[i]); gl->programs[i] = program; gl->flags |= map[i].flag; - fprintf(stderr, "%s Program Updated!\n", map[i].name); + stream_push_s8s(err, 2, (s8 []){map[i].name, s8(" Program Updated!\n")}); + } + + if (err->widx) { + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; } } static void -usage(char *argv0) +usage(char *argv0, Stream *err) { - os_write_err_msg(s8("usage: ")); - os_write_err_msg(c_str_to_s8(argv0)); - os_fatal(s8(" [-v] [-g COLxROW]\n")); + stream_push_s8(err, s8("usage: ")); + stream_push_s8(err, c_str_to_s8(argv0)); + stream_push_s8(err, s8(" [-v] [-g COLxROW]\n")); + os_fatal(stream_to_s8(err)); } i32 -main(i32 argc, char *argv[]) +main(i32 argc, char *argv[], char *envp[]) { Arena memory = os_new_arena(16 * MEGABYTE); Term term = {0}; + term.error_stream = stream_alloc(&memory, MEGABYTE / 4); + iv2 cells = {.x = -1, .y = -1}; char *argv0 = *argv++; @@ -333,34 +357,42 @@ main(i32 argc, char *argv[]) for (i32 i = 0; i < argc; i++) { char *arg = argv[i]; if (!arg || !arg[0]) - usage(argv0); + usage(argv0, &term.error_stream); if (arg[0] != '-') break; arg++; switch (arg[0]) { case 'g': if (!argv[i + 1]) - usage(argv0); + usage(argv0, &term.error_stream); cres = i32_from_cstr(argv[i + 1], 'x'); if (cres.status == CR_SUCCESS) cells.w = cres.i; cres = i32_from_cstr(cres.unparsed, 0); if (cres.status == CR_SUCCESS) cells.h = cres.i; - if (cells.w <= 0 || cells.h <= 0) - fprintf(stderr, "ignoring malformed geometry: %s\n", argv[i + 1]); + if (cells.w <= 0 || cells.h <= 0) { + stream_push_s8(&term.error_stream, s8("ignoring malformed geometry: ")); + stream_push_s8(&term.error_stream, c_str_to_s8(argv[i + 1])); + stream_push_byte(&term.error_stream, '\n'); + } argv++; argc--; break; case 'v': - os_write_err_msg(c_str_to_s8(argv0)); - os_fatal(s8(" " VERSION "\n")); + stream_push_s8s(&term.error_stream, 2, + (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); + os_fatal(stream_to_s8(&term.error_stream)); default: - usage(argv0); + usage(argv0, &term.error_stream); } } + if (term.error_stream.widx) { + os_write_err_msg(stream_to_s8(&term.error_stream)); + term.error_stream.widx = 0; + } - do_debug(NULL); + do_debug(NULL, &term.error_stream); if (!glfwInit()) os_fatal(s8("Failed to init GLFW\n")); @@ -397,8 +429,8 @@ main(i32 argc, char *argv[]) f32 last_time = 0; while (!glfwWindowShouldClose(term.gl.window)) { - do_debug(&term.gl); - check_shaders(&term.gl, memory); + do_debug(&term.gl, &term.error_stream); + check_shaders(&term.gl, memory, &term.error_stream); f32 current_time = (f32)glfwGetTime(); f32 dt = current_time - last_time; diff --git a/os_unix.c b/os_unix.c @@ -2,7 +2,6 @@ #include <fcntl.h> #include <pty.h> #include <pwd.h> -#include <stdio.h> #include <sys/mman.h> #include <sys/select.h> #include <sys/stat.h> @@ -79,6 +78,46 @@ os_filetime_changed(os_filetime a, os_filetime b) return ((a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec)) != 0; } +static u32 +os_file_attribute_to_mode(u32 attr) +{ + u32 result = O_CREAT; + if (attr & FA_READ && attr & FA_WRITE) { + result |= O_RDWR; + } else if (attr & FA_READ) { + result |= O_RDONLY; + } else if (attr & FA_WRITE) { + result |= O_WRONLY; + } + + if (attr & FA_APPEND) + result |= O_APPEND; + + return result; +} + +static iptr +os_open(u8 *name, u32 attr) +{ + iptr result = open((char *)name, os_file_attribute_to_mode(attr), 0660); + if (result < 0) + result = INVALID_FILE; + return result; +} + +static b32 +os_write(iptr file, s8 raw, size offset) +{ + size result = pwrite(file, raw.data, raw.len, offset); + return result == raw.len; +} + +static void +os_close(iptr file) +{ + close(file); +} + static s8 os_read_file(Arena *a, char *name, size filesize) { @@ -148,8 +187,12 @@ os_alloc_ring_buffer(RingBuf *rb, size capacity) void *ret = mmap(rb->buf + i * rb->cap, rb->cap, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0); if (ret == MAP_FAILED) { - fprintf(stderr, "os_alloc_ring_buffer: mmap(%d) failed\n", i); - _exit(1); + u8 buf[256]; + Stream err = {.buf = buf, .cap = sizeof(buf)}; + stream_push_s8(&err, s8("os_alloc_ring_buffer: mmap(")); + stream_push_u64(&err, i); + stream_push_s8(&err, s8(") failed\n")); + os_fatal(stream_to_s8(&err)); } } close(fd); diff --git a/terminal.c b/terminal.c @@ -365,31 +365,42 @@ term_reset(Term *t) } static void -dump_csi(CSI *csi) +dump_csi(CSI *csi, Stream *err) { - os_write_err_msg(s8("raw: ESC[")); + stream_push_s8(err, s8("raw: ESC[")); for (size i = 0; i < csi->raw.len; i++) { u8 c = csi->raw.data[i]; - if (ISPRINT(c)) - os_write_err_msg((s8){.len = 1, .data = csi->raw.data + i}); - else if (c == '\n') - os_write_err_msg(s8("\\n")); - else if (c == '\r') - os_write_err_msg(s8("\\r")); - else - fprintf(stderr, "\\x%02X", c); + if (ISPRINT(c)) { + stream_push_byte(err, csi->raw.data[i]); + } else if (c == '\n') { + stream_push_s8(err, s8("\\n")); + } else if (c == '\r') { + stream_push_s8(err, s8("\\r")); + } else { + stream_push_s8(err, s8("\\x")); + stream_push_hex_u64(err, c); + } } - fprintf(stderr, "\n\tparsed = { .priv = %d, .mode = ", csi->priv); + stream_push_s8(err, s8("\n\tparsed = { .priv = ")); + stream_push_u64(err, csi->priv); + stream_push_s8(err, s8(" .mode = ")); if (ISPRINT(csi->mode)) { - u8 buf[1] = {csi->mode}; - os_write_err_msg((s8){.len = 1, .data = buf}); + stream_push_byte(err, csi->mode); } else { - fprintf(stderr, "\\x%02X", csi->mode); + stream_push_s8(err, s8("\\x")); + stream_push_hex_u64(err, csi->mode); + } + stream_push_s8(err, s8(", .argc = ")); + stream_push_u64(err, csi->argc); + stream_push_s8(err, s8(", .argv = {")); + for (i32 i = 0; i < csi->argc; i++) { + stream_push_byte(err, ' '); + stream_push_i64(err, csi->argv[i]); } - fprintf(stderr, ", .argc = %d, .argv = {", csi->argc); - for (i32 i = 0; i < csi->argc; i++) - fprintf(stderr, " %d", csi->argv[i]); - os_write_err_msg(s8(" } }\n")); + + stream_push_s8(err, s8(" } }\n")); + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; } /* ED/DECSED: Erase in Display */ @@ -474,7 +485,11 @@ clear_term_tab(Term *t, i32 arg) t->tabs[i] = 0; break; default: - fprintf(stderr, "clear_term_tab: unhandled arg: %d\n", arg); + stream_push_s8(&t->error_stream, s8("clear_term_tab: unhandled arg: ")); + stream_push_i64(&t->error_stream, arg); + stream_push_byte(&t->error_stream, '\n'); + os_write_err_msg(stream_to_s8(&t->error_stream)); + t->error_stream.widx = 0; } } @@ -552,7 +567,7 @@ set_mode(Term *t, CSI *csi, b32 set, b32 simulate) break; default: os_write_err_msg(s8("set_mode: unhandled mode: ")); - dump_csi(csi); + dump_csi(csi, &t->error_stream); } } #undef PRIV @@ -584,13 +599,15 @@ indexed_colour(i32 index) } static struct conversion_result -direct_colour(i32 *argv, i32 argc, i32 *idx) +direct_colour(i32 *argv, i32 argc, i32 *idx, Stream *err) { struct conversion_result result = {.status = CR_FAILURE}; switch (argv[*idx + 1]) { case 2: /* NOTE: defined RGB colour */ if (*idx + 4 >= argc) { - fprintf(stderr, "direct_colour: wrong paramater count: %d\n", argc); + stream_push_s8(err, s8("direct_colour: wrong parameter count: ")); + stream_push_u64(err, argc); + stream_push_byte(err, '\n'); break; } u32 r = (u32)argv[*idx + 2]; @@ -598,7 +615,13 @@ direct_colour(i32 *argv, i32 argc, i32 *idx) u32 b = (u32)argv[*idx + 4]; *idx += 4; if (r > 0xFF || g > 0xFF || b > 0xFF) { - fprintf(stderr, "direct_colour: bad rgb colour: (%u, %u, %u)\n", r, g, b); + stream_push_s8(err, s8("direct_colour: bad rgb colour: (")); + stream_push_u64(err, r); + stream_push_s8(err, s8(", ")); + stream_push_u64(err, g); + stream_push_s8(err, s8(", ")); + stream_push_u64(err, b); + stream_push_s8(err, s8(")\n")); break; } result.colour = (Colour){.r = r, .g = g, .b = b, .a = 0xFF}; @@ -606,13 +629,16 @@ direct_colour(i32 *argv, i32 argc, i32 *idx) break; case 5: /* NOTE: indexed colour */ if (*idx + 2 >= argc) { - fprintf(stderr, "direct_colour: wrong paramater count: %d\n", argc); + stream_push_s8(err, s8("direct_colour: wrong parameter count: ")); + stream_push_u64(err, argc); + stream_push_byte(err, '\n'); break; } *idx += 2; if (!BETWEEN(argv[*idx], 0, 255)) { - fprintf(stderr, "direct_colour: index parameter out of range: %d\n", - argv[*idx]); + stream_push_s8(err, s8("direct_colour: index parameter out of range: ")); + stream_push_i64(err, argv[*idx]); + stream_push_byte(err, '\n'); break; } if (BETWEEN(argv[*idx], 0, 16)) @@ -622,8 +648,11 @@ direct_colour(i32 *argv, i32 argc, i32 *idx) result.status = CR_SUCCESS; break; default: - fprintf(stderr, "define_colour: unknown argument: %d\n", argv[*idx + 1]); + stream_push_s8(err, s8("direct_colour: unknown argument: ")); + stream_push_i64(err, argv[*idx + 1]); + stream_push_byte(err, '\n'); } + return result; } @@ -653,24 +682,24 @@ set_colours(Term *t, CSI *csi) case 28: cs->attr &= ~ATTR_INVISIBLE; break; case 29: cs->attr &= ~ATTR_STRUCK; break; case 38: - dcr = direct_colour(csi->argv, csi->argc, &i); + dcr = direct_colour(csi->argv, csi->argc, &i, &t->error_stream); if (dcr.status == CR_SUCCESS) { cs->fg = dcr.colour; } else { - os_write_err_msg(s8("set_colours: ")); - dump_csi(csi); + stream_push_s8(&t->error_stream, s8("set_colours: ")); + dump_csi(csi, &t->error_stream); } break; case 39: cs->fg = g_colours.data[g_colours.fgidx]; break; case 48: - dcr = direct_colour(csi->argv, csi->argc, &i); + dcr = direct_colour(csi->argv, csi->argc, &i, &t->error_stream); if (dcr.status == CR_SUCCESS) { cs->bg = dcr.colour; } else { - os_write_err_msg(s8("set_colours: ")); - dump_csi(csi); + stream_push_s8(&t->error_stream, s8("set_colours: ")); + dump_csi(csi, &t->error_stream); } break; @@ -686,8 +715,10 @@ set_colours(Term *t, CSI *csi) } else if (BETWEEN(csi->argv[i], 100, 107)) { cs->bg = g_colours.data[csi->argv[i] - 92]; } else { - fprintf(stderr, "unhandled colour arg: %d\n", csi->argv[i]); - dump_csi(csi); + stream_push_s8(&t->error_stream, s8("unhandled colour arg: ")); + stream_push_i64(&t->error_stream, csi->argv[i]); + stream_push_byte(&t->error_stream, '\n'); + dump_csi(csi, &t->error_stream); } } } @@ -724,8 +755,10 @@ window_manipulation(Term *t, CSI *csi) break; case 23: glfwSetWindowTitle(t->gl.window, t->saved_title); break; default: - fprintf(stderr, "unhandled xtwinops: %d\n", csi->argv[0]); - dump_csi(csi); + stream_push_s8(&t->error_stream, s8("unhandled xtwinops: ")); + stream_push_i64(&t->error_stream, csi->argv[0]); + stream_push_byte(&t->error_stream, '\n'); + dump_csi(csi, &t->error_stream); } } @@ -845,8 +878,8 @@ handle_csi(Term *t, CSI *csi) goto unknown; default: unknown: - os_write_err_msg(s8("unknown csi: ")); - dump_csi(csi); + stream_push_s8(&t->error_stream, s8("unknown csi: ")); + dump_csi(csi, &t->error_stream); } END_TIMED_BLOCK(); } @@ -910,23 +943,31 @@ reset_csi(CSI *csi, s8 *raw) } static void -dump_osc(OSC *osc) +dump_osc(OSC *osc, Stream *err) { - os_write_err_msg(s8("ESC]")); + stream_push_s8(err, s8("ESC]")); for (size i = 0; i < osc->raw.len; i++) { u8 cp = osc->raw.data[i]; - if (ISPRINT(cp)) - os_write_err_msg((s8){.len = 1, .data = osc->raw.data + i}); - else if (cp == '\n') - os_write_err_msg(s8("\\n")); - else if (cp == '\r') - os_write_err_msg(s8("\\r")); - else if (cp == '\a') - os_write_err_msg(s8("\\a")); - else - fprintf(stderr, "\\x%02X", cp); + if (ISPRINT(cp)) { + stream_push_byte(err, cp); + } else if (cp == '\n') { + stream_push_s8(err, s8("\\n")); + } else if (cp == '\r') { + stream_push_s8(err, s8("\\r")); + } else if (cp == '\a') { + stream_push_s8(err, s8("\\a")); + } else { + stream_push_s8(err, s8("\\x")); + stream_push_hex_u64(err, cp); + } } - fprintf(stderr, "\n\t.cmd = %d, .arg = {.len = %zd}\n", osc->cmd, osc->arg.len); + stream_push_s8(err, s8("\n\t.cmd = ")); + stream_push_u64(err, osc->cmd); + stream_push_s8(err, s8(", .arg = {.len = ")); + stream_push_i64(err, osc->arg.len); + stream_push_s8(err, s8("}\n")); + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; } static void @@ -942,8 +983,8 @@ handle_osc(Term *t, s8 *raw, Arena a) case 1: /* IGNORED: set icon name */ break; case 2: set_window_title(t->gl.window, a, osc.arg); break; default: - os_write_err_msg(s8("unhandled osc cmd: ")); - dump_osc(&osc); + stream_push_s8(&t->error_stream, s8("unhandled osc cmd: ")); + dump_osc(&osc, &t->error_stream); break; } END_TIMED_BLOCK(); @@ -996,7 +1037,13 @@ handle_escape(Term *t, s8 *raw, Arena a) cursor_alt(t, 0); break; default: - fprintf(stderr, "unknown escape sequence: ESC %c (0x%02x)\n", cp, cp); + stream_push_s8(&t->error_stream, s8("unknown escape sequence: ESC ")); + stream_push_byte(&t->error_stream, cp); + stream_push_s8(&t->error_stream, s8(" (0x")); + stream_push_hex_u64(&t->error_stream, cp); + stream_push_s8(&t->error_stream, s8(")\n")); + os_write_err_msg(stream_to_s8(&t->error_stream)); + t->error_stream.widx = 0; break; } END_TIMED_BLOCK(); diff --git a/test.c b/test.c @@ -49,6 +49,9 @@ get_gpu_glyph_index(Arena a, void *b, void *c, u32 cp, u32 d, u32 e, CachedGlyph #define ESC(a) s8("\x1B"#a) #define CSI(a) ESC([a) +static s8 failure_string = s8("\x1B[31mFAILURE\x1B[0m\n"); +static s8 success_string = s8("\x1B[32mSUCCESS\x1B[0m\n"); + struct test_result { b32 status; const char *info; }; #define TEST_FN(name) struct test_result name(Term *term, Arena arena) typedef TEST_FN(Test_Fn); @@ -298,9 +301,10 @@ DebugRecord debug_records[__COUNTER__]; int main(void) { - Arena memory = os_new_arena(16 * MEGABYTE); + Arena memory = os_new_arena(32 * MEGABYTE); Term term = {0}; + Stream log = stream_alloc(&memory, 4 * MEGABYTE); for (u32 i = 0; i < ARRAY_COUNT(term.saved_cursors); i++) { cursor_reset(&term); cursor_move_to(&term, 0, 0); @@ -323,12 +327,23 @@ main(void) buffer_reset(&term); term_reset(&term); struct test_result result = tests[i](&term, memory); + s8 fn = c_str_to_s8((char *)result.info); + stream_push_s8(&log, fn); + stream_push_s8(&log, s8(":")); + size count = fn.len; + while (count < 26) { stream_push_byte(&log, ' '); count++; } if (result.status == 0) { failure_count++; - printf("TEST FAILED: [%u/%lu] %s\n", i, ARRAY_COUNT(tests), result.info); + stream_push_s8(&log, failure_string); + } else { + stream_push_s8(&log, success_string); } } - printf("FINISHED: [%lu/%lu] Succeeded\n", ARRAY_COUNT(tests) - failure_count, - ARRAY_COUNT(tests)); + stream_push_s8(&log, s8("FINISHED: [")); + stream_push_u64(&log, ARRAY_COUNT(tests) - failure_count); + stream_push_byte(&log, '/'); + stream_push_u64(&log, ARRAY_COUNT(tests)); + stream_push_s8(&log, s8("] Succeeded\n")); + os_write_err_msg(stream_to_s8(&log)); return 0; } diff --git a/util.c b/util.c @@ -180,6 +180,139 @@ i32_from_cstr(char *s, char delim) return ret; } +static Stream +stream_alloc(Arena *a, u32 cap) +{ + Stream result = {0}; + result.cap = cap; + result.buf = alloc(a, typeof(*result.buf), cap); + return result; +} + +static s8 +stream_to_s8(Stream *s) +{ + s8 result = {.len = s->widx, .data = s->buf}; + return result; +} + +static void +stream_push_byte(Stream *s, u8 cp) +{ + s->errors |= !(s->cap - s->widx); + if (!s->errors) + s->buf[s->widx++] = cp; +} + +static void +stream_push_s8(Stream *s, s8 str) +{ + s->errors |= (s->cap - s->widx) < str.len; + if (!s->errors) { + for (size i = 0; i < str.len; i++) + s->buf[s->widx++] = str.data[i]; + } +} + +/* NOTE: how can he get away with not using a library for this */ +static void +stream_push_s8_left_padded(Stream *s, s8 str, u32 width) +{ + for (u32 i = str.len; i < width; i++) + stream_push_byte(s, ' '); + stream_push_s8(s, str); +} + +static void +stream_push_s8s(Stream *s, u32 count, s8 *strs) +{ + while (count) { stream_push_s8(s, *strs++); count--; } +} + +static void +stream_push_hex_u64(Stream *s, u64 n) +{ + static u8 hex[16] = {"0123456789abcdef"}; + u8 buf[16]; + u8 *end = buf + sizeof(buf); + u8 *beg = end; + while (n) { + *--beg = hex[n & 0x0F]; + n >>= 4; + } + if (beg == end) { + *--beg = '0'; + *--beg = '0'; + } + stream_push_s8(s, (s8){.len = end - beg, .data = beg}); +} + +static void +stream_push_u64_padded(Stream *s, u64 n, u32 width) +{ + u8 tmp[64]; + u8 *end = tmp + sizeof(tmp); + u8 *beg = end; + do { *--beg = '0' + (n % 10); } while (n /= 10); + + s8 str = {.len = end - beg, .data = beg}; + for (u32 i = str.len; i < width; i++) + stream_push_byte(s, ' '); + stream_push_s8(s, str); +} + +static void +stream_push_u64(Stream *s, u64 n) +{ + stream_push_u64_padded(s, n, 0); +} + +static void +stream_push_i64(Stream *s, i64 n) +{ + if (n < 0) { + stream_push_byte(s, '-'); + n = -n; + } + stream_push_u64_padded(s, n, 0); +} + +static void +stream_push_iv2(Stream *s, iv2 v) +{ + stream_push_byte(s, '{'); + stream_push_i64(s, v.x); + stream_push_byte(s, ','); + stream_push_i64(s, v.y); + stream_push_byte(s, '}'); +} + +static void +stream_push_f64(Stream *s, f64 f, i64 prec) +{ + if (f < 0) { + stream_push_byte(s, '-'); + f *= -1; + } + + /* NOTE: round last digit */ + f += 0.5f / prec; + + if (f >= (f64)(-1UL >> 1)) { + stream_push_s8(s, s8("inf")); + } else { + u64 integral = f; + u64 fraction = (f - integral) * prec; + stream_push_u64(s, integral); + stream_push_byte(s, '.'); + for (u64 i = prec / 10; i > 1; i /= 10) { + if (i > fraction) + stream_push_byte(s, '0'); + } + stream_push_u64(s, fraction); + } +} + static s8 utf8_encode(u32 cp) { diff --git a/util.h b/util.h @@ -55,7 +55,9 @@ typedef uint32_t u32; typedef uint32_t b32; typedef int64_t i64; typedef uint64_t u64; +typedef ptrdiff_t iptr; typedef ptrdiff_t size; +typedef size_t usize; typedef union { struct { i32 x, y; }; @@ -97,6 +99,20 @@ typedef struct { size len; u8 *data; } s8; typedef struct { iv2 start, end; } Range; #define INVALID_RANGE_END (iv2){.x = -1, .y = -1} +typedef struct { + u8 *buf; + u32 cap; + u32 widx; + b32 errors; +} Stream; + +#define INVALID_FILE (-1) +enum file_attribute { + FA_READ = 1 << 0, + FA_WRITE = 1 << 1, + FA_APPEND = 1 << 2, +}; + #include "debug.h" enum cell_attribute { @@ -453,6 +469,8 @@ typedef struct { u32 tabs[32]; char saved_title[1024]; + + Stream error_stream; } Term; static f32 dt_for_frame; diff --git a/vtgl.c b/vtgl.c @@ -92,7 +92,11 @@ resize(Term *t) if (t->size.w > ARRAY_COUNT(t->tabs) * 32) { t->size.w = ARRAY_COUNT(t->tabs) * 32u; - fprintf(stderr, "resize: max terminal width is %u; clamping\n", t->size.w); + stream_push_s8(&t->error_stream, s8("resize: max terminal width is ")); + stream_push_u64(&t->error_stream, t->size.w); + stream_push_s8(&t->error_stream, s8("; clamping\n")); + os_write_err_msg(stream_to_s8(&t->error_stream)); + t->error_stream.widx = 0; } if (!equal_uv2(old_size, t->size)) { @@ -946,7 +950,6 @@ draw_debug_overlay(Term *t, RenderCtx *rc) return; v2 ws = t->gl.window_size; - s8 buf = s8alloc(&t->arena_for_frame, 1024); static GlyphCacheStats glyph_stats; static Rect r; @@ -964,12 +967,14 @@ draw_debug_overlay(Term *t, RenderCtx *rc) f32 line_pad = 5; v2 txt_pos = {.x = r.pos.x, .y = ws.h - 20}; + Stream txt = stream_alloc(&t->arena_for_frame, 1024); { - s8 txt = buf; - txt.len = snprintf((char *)txt.data, buf.len, "Render Time: %0.02f ms/f", dt_for_frame * 1e3); - v2 ts = measure_text(rc, font_id, txt); + stream_push_s8(&txt, s8("Render Time: ")); + stream_push_f64(&txt, dt_for_frame * 1e3, 100); + stream_push_s8(&txt, s8(" ms/f")); + v2 ts = measure_text(rc, font_id, stream_to_s8(&txt)); txt_pos.y = (u32)(txt_pos.y - ts.h - line_pad); - push_s8(rc, txt_pos, fg, font_id, txt); + push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt)); if (ts.w > max_text_width) max_text_width = ts.w; if (ts.h > line_height) line_height = ts.h; @@ -981,12 +986,18 @@ draw_debug_overlay(Term *t, RenderCtx *rc) DebugRecord *dr = debug_records + i; - s8 txt = buf; - txt.len = snprintf((char *)txt.data, buf.len, "%29s: %9lu cycs %5u %4s %11.02f cycs/hit", - dr->function_name, cycs[i], hits[i], hits[i] > 1? "hits" : "hit", - (f32)cycs[i]/(f32)hits[i]); + txt.widx = 0; + stream_push_s8_left_padded(&txt, c_str_to_s8(dr->function_name), 29); + stream_push_byte(&txt, ':'); + stream_push_u64_padded(&txt, cycs[i], 10); + stream_push_s8(&txt, s8(" cycs ")); + stream_push_u64_padded(&txt, hits[i], 5); + stream_push_s8_left_padded(&txt, hits[i] > 1? s8("hits") : s8("hit"), 5); + stream_push_s8(&txt, s8(" ")); + stream_push_f64(&txt, (f32)cycs[i]/(f32)hits[i], 100); + stream_push_s8_left_padded(&txt, s8("cycs/hit"), 80 - txt.widx); txt_pos.y = (u32)(txt_pos.y - line_height - line_pad); - v2 ts = push_s8(rc, txt_pos, fg, font_id, txt); + v2 ts = push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt)); if (ts.w > max_text_width) max_text_width = ts.w; if (ts.h > line_height) line_height = ts.h; @@ -1001,16 +1012,17 @@ draw_debug_overlay(Term *t, RenderCtx *rc) v2 ts = push_s8(rc, txt_pos, fg, font_id, header); /* TODO: This doesn't really work when we are redrawing on every frame */ - static char *fmts[ARRAY_COUNT(glyph_stats.E)] = { - " Hits: %u", - " Misses: %u", - " Recycles: %u", + static s8 fmts[ARRAY_COUNT(glyph_stats.E)] = { + s8(" Hits: "), + s8(" Misses: "), + s8(" Recycles: "), }; for (u32 i = 0; i < ARRAY_COUNT(glyph_stats.E); i++) { - s8 txt = buf; - txt.len = snprintf((char *)txt.data, buf.len, fmts[i], glyph_stats.E[i]); + txt.widx = 0; + stream_push_s8(&txt, fmts[i]); + stream_push_u64(&txt, glyph_stats.E[i]); txt_pos.y = (u32)(txt_pos.y - line_height - line_pad); - ts = push_s8(rc, txt_pos, fg, font_id, txt); + ts = push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt)); if (ts.w > max_text_width) max_text_width = ts.w; if (ts.h > line_height) line_height = ts.h;