code wiki / (root) / nx_game_harness.nx

nx_game_harness.nx source

↩ module page · 77 lines · 2828 B

1// nx_game_harness.nx -- fd-99 capture mechanism for the bits-up test 2// driver. Replaces the "renderChunks" buffer in nishi-host.js. Game's 3// bits-up render fn writes to sentinel fd 99; we redirect 99 to a temp 4// file via open + dup3; after render, lseek-rewind + read to retrieve. 5// 6// Why a tempfile (not pipe2): NishiLang's *i64 indexing of the pipefds 7// array writes a 16-byte buffer where the kernel writes only 8 bytes 8// (two int fds), creating subtle layout issues. Tempfile is unambiguous: 9// open returns one fd, we own the buffer, lseek rewinds for read. 10// 11// Usage: 12// let cap = nx_capture_start() 13// nx_<game>_render(state) 14// let buf = sys_mmap(65536) 15// let n = nx_capture_end(cap, buf, 65536) 16// // buf[0..n] now contains exactly what the game emitted. 17 18import "nx_syscalls.nx" 19 20const NX_CAPTURE_FD: i64 = 99 21 22// open() flags (POSIX -- same on RV64 + x86_64 Linux). 23const _O_RDWR: i64 = 2 24const _O_CREAT: i64 = 64 // 0100 octal 25const _O_TRUNC: i64 = 512 // 01000 octal 26 27const _SEEK_SET: i64 = 0 28 29// openat() with AT_FDCWD does what open() does. sys_openat_wr from 30// nx_syscalls.nx assumes O_CREAT|O_WRONLY|O_TRUNC -- we want O_RDWR 31// so we can read back, so call openat directly with our own flags. 32func _gh_openat_rdwr(path: *u8, mode: i64) -> i64 { 33 let flags: i64 = _O_RDWR + _O_CREAT + _O_TRUNC 34 return __syscall(SYS_OPENAT, AT_FDCWD, path, flags, mode, 0, 0) 35} 36 37struct NxGameCapture { 38 backing_fd: i64, // tempfile fd; -1 on error 39 bytes_captured: i64 40} 41 42func nx_capture_start() -> *NxGameCapture { 43 let cap: *NxGameCapture = sys_mmap(16) as *NxGameCapture 44 cap.backing_fd = -1 45 cap.bytes_captured = 0 46 47 // Use a unique-enough tempfile path. We could use mkstemp, but a 48 // single-test-per-process pattern means a fixed path is fine; if a 49 // second test runs in parallel they'd both want fd 99 anyway. 50 let path: *u8 = "/tmp/nx_capture.html" as *u8 51 let fd: i64 = _gh_openat_rdwr(path, 0o644 as i64) 52 if fd < 0 { return cap } 53 sys_dup3(fd, NX_CAPTURE_FD, 0 as i64) 54 // Keep both fds; close(99) at end signals render-done, close(fd) reads. 55 cap.backing_fd = fd 56 return cap 57} 58 59func nx_capture_end(cap: *NxGameCapture, out: *u8, out_cap: i64) -> i64 { 60 if cap.backing_fd < 0 { return -1 } 61 sys_close(NX_CAPTURE_FD) 62 sys_lseek(cap.backing_fd, 0 as i64, _SEEK_SET) 63 var total: i64 = 0 64 var done: i64 = 0 65 while done == 0 { 66 if total >= out_cap { done = 1 } 67 if done == 0 { 68 let dst: *u8 = ((out as i64) + total) as *u8 69 let rc: i64 = sys_read(cap.backing_fd, dst, out_cap - total) 70 if rc <= 0 { done = 1 } 71 if done == 0 { total = total + rc } 72 } 73 } 74 sys_close(cap.backing_fd) 75 cap.bytes_captured = total 76 return total 77}