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}