code wiki / (root) / nx_desktop_game.nx

nx_desktop_game.nx source

↩ module page · 186 lines · 7352 B

1// nx_desktop_game.nx -- the shared NATIVE GAME SURFACE (no wasm, no JS, no third-party anything): 2// window + server keymap + event pump into nx_input_abstract + banded framebuffer blit, extracted 3// from nx_desktop_breeders the moment a second native client (craft) needed the same machinery -- 4// migrate-on-touch, one definition. A client composes: dg_setup -> dg_keymap -> loop { dg_pump -> 5// its own step/render -> dg_blit } -- the game organ stays byte-identical to its wasm build; only 6// the surface differs. dg_prove_surface is the shared selftest core (blit accepted + pixmap put->get 7// byte round-trip) so every client's proof battery stays honest without re-implementing it. 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_x11_paint.nx" 11import "nx_x11_pixmap.nx" 12import "nx_x11_get_image.nx" 13import "nx_x11_put_image.nx" 14import "nx_x11_keyboard.nx" 15import "nx_input_abstract.nx" 16const DG_MAGIC_4096: i64 = 4096 17 18const DG_FRAME_MS: i64 = 33 19 20// poll the X fd for readability; the timeout doubles as the frame pacer. pfd = caller's 8-byte buf. 21func dg_poll_in(pfd: *u8, fd: i64, timeout_ms: i64) -> i64 { 22 pfd[0] = (fd & 255) as u8 23 pfd[1] = ((fd >> 8) & 255) as u8 24 pfd[2] = ((fd >> 16) & 255) as u8 25 pfd[3] = ((fd >> 24) & 255) as u8 26 pfd[4] = 1 // POLLIN 27 pfd[5] = 0 28 pfd[6] = 0 29 pfd[7] = 0 30 let r: i64 = sys_poll(pfd, 1, timeout_ms) 31 if r <= 0 { return 0 } 32 if (pfd[6] & 0xff) != 0 { return 1 } 33 return 0 34} 35 36// one X event into the input layer. Returns 1 = quit (ESC / DestroyNotify). Synthetic events 37// (SendEvent) arrive with bit 0x80 set and take the SAME path -- deliberately. 38func dg_dispatch(inp: *i64, ev: *u8, vk_table: *i64) -> i64 { 39 let kind: i64 = ((ev[0] & 0xff) as i64) & 0x7f 40 if kind == 2 { 41 let vk: i64 = vk_table[(ev[1] & 0xff) as i64] 42 if vk == 27 { return 1 } 43 if vk != 0 { ia_key(inp, vk, 1) } 44 return 0 45 } 46 if kind == 3 { 47 let vk: i64 = vk_table[(ev[1] & 0xff) as i64] 48 if vk != 0 { ia_key(inp, vk, 0) } 49 return 0 50 } 51 if kind == 17 { return 1 } // DestroyNotify 52 return 0 53} 54 55// drain every pending event without blocking. Returns 1 = quit requested / connection gone. 56func dg_pump(conn: *X11Conn, inp: *i64, ev: *u8, pfd: *u8, vk_table: *i64) -> i64 { 57 var quit: i64 = 0 58 var more: i64 = 1 59 while more == 1 { 60 if dg_poll_in(pfd, conn.fd, 0) == 0 { more = 0 } 61 if more == 1 { 62 if nx_x11_read_event(conn, ev) < 0 { return 1 } 63 if dg_dispatch(inp, ev, vk_table) == 1 { quit = 1 } 64 } 65 } 66 return quit 67} 68 69// mouse-aware dispatch: everything dg_dispatch does PLUS mouse buttons through the standard- 70// gamepad door (button 1 -> pad 0 = A/break, button 3 -> pad 1 = B/place) and MotionNotify into 71// the analog look channel as relative deltas. mctx: [0]=lastx [1]=lasty [2]=have-last [3]=motion-seen. 72func dg_dispatch2(inp: *i64, ev: *u8, vk_table: *i64, mctx: *i64) -> i64 { 73 let kind: i64 = ((ev[0] & 0xff) as i64) & 0x7f 74 if kind == 4 { 75 let btn: i64 = (ev[1] & 0xff) as i64 76 if btn == 1 { ia_pad(inp, 0, 1) } 77 if btn == 3 { ia_pad(inp, 1, 1) } 78 return 0 79 } 80 if kind == 5 { 81 let btn: i64 = (ev[1] & 0xff) as i64 82 if btn == 1 { ia_pad(inp, 0, 0) } 83 if btn == 3 { ia_pad(inp, 1, 0) } 84 return 0 85 } 86 if kind == 6 { 87 let ex2: i64 = ((ev[24] & 0xff) as i64) | (((ev[25] & 0xff) as i64) << 8) 88 let ey2: i64 = ((ev[26] & 0xff) as i64) | (((ev[27] & 0xff) as i64) << 8) 89 if mctx[2] == 1 { ia_look(inp, ex2 - mctx[0], ey2 - mctx[1]) } 90 mctx[0] = ex2 91 mctx[1] = ey2 92 mctx[2] = 1 93 mctx[3] = 1 94 return 0 95 } 96 return dg_dispatch(inp, ev, vk_table) 97} 98 99// mouse-aware pump: drains events through dg_dispatch2, then recenters the pointer ONCE if any 100// motion arrived (the FPS warp -- deltas never die at the window edge). Returns 1 = quit. 101func dg_pump2(conn: *X11Conn, wid: i64, w: i64, h: i64, 102 inp: *i64, ev: *u8, pfd: *u8, vk_table: *i64, mctx: *i64) -> i64 { 103 var quit: i64 = 0 104 var more: i64 = 1 105 mctx[3] = 0 106 while more == 1 { 107 if dg_poll_in(pfd, conn.fd, 0) == 0 { more = 0 } 108 if more == 1 { 109 if nx_x11_read_event(conn, ev) < 0 { return 1 } 110 if dg_dispatch2(inp, ev, vk_table, mctx) == 1 { quit = 1 } 111 } 112 } 113 if mctx[3] == 1 { 114 nx_x11_warp_pointer(conn, wid, w/2, h/2) 115 mctx[0] = w/2 116 mctx[1] = h/2 117 } 118 return quit 119} 120 121// convert + banded-blit a packed-i64 framebuffer into the window. Returns bands or negative. 122func dg_blit(conn: *X11Conn, wid: i64, gc: i64, fbp: *i64, w: i64, h: i64, zbuf: *u8) -> i64 { 123 nx_fb_to_zpixmap(fbp, w*h, zbuf) 124 return nx_x11_put_image_banded(conn, wid, gc, w, h, zbuf) 125} 126 127// connect + window + name + gc + map + flush. out[0]=conn as i64, [1]=wid, [2]=gc, [3]=flush rc. 128// Returns 0 or a negative stage code. 129func dg_setup(title: *u8, tlen: i64, w: i64, h: i64, out: *i64) -> i64 { 130 let cr: i64 = nx_x11_connect_local(0) 131 if cr < 0 { return 0 - 1 } 132 let conn: *X11Conn = cr as *X11Conn 133 var emask: i64 = NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_KEY_RELEASE | NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_STRUCTURE 134 emask = emask | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_BUTTON_RELEASE | NX_X11_EVENT_POINTER_MOTION 135 let wid: i64 = nx_x11_create_window(conn, 40, 40, w, h, 0x08070f, emask) 136 if wid < 0 { return 0 - 2 } 137 nx_x11_set_wm_name(conn, wid, title, tlen) 138 let gc: i64 = nx_x11_create_gc(conn, wid, 0) 139 if gc < 0 { return 0 - 3 } 140 nx_x11_map_window(conn, wid) 141 out[0] = cr 142 out[1] = wid 143 out[2] = gc 144 out[3] = nx_x11_flush_via_get_input_focus(conn) 145 return 0 146} 147 148// first keycode mapped to a vk, 0 if none (selftest helper) 149func dg_kc_for(vk_table: *i64, vk: i64) -> i64 { 150 var i: i64 = 0 151 while i < NX_X11_KEYMAP_N { 152 if vk_table[i] == vk { return i } 153 i = i + 1 154 } 155 return 0 156} 157 158// the shared surface proof: blit the caller's frame, confirm the server accepted it, then round-trip 159// the SAME bytes through an off-screen pixmap and byte-compare B,G,R. out[0]=bands [1]=flush rc 160// [2]=get bytes [3]=mismatched BGR bytes. Returns 0, or negative on a wire failure. 161func dg_prove_surface(conn: *X11Conn, wid: i64, gc: i64, fbp: *i64, 162 w: i64, h: i64, zbuf: *u8, out: *i64) -> i64 { 163 out[0] = dg_blit(conn, wid, gc, fbp, w, h, zbuf) 164 out[1] = nx_x11_flush_via_get_input_focus(conn) 165 let pid: i64 = nx_x11_create_pixmap(conn, wid, w, h, conn.root_depth) 166 nx_x11_put_image_banded(conn, pid, gc, w, h, zbuf) 167 let gbuf: *u8 = sys_mmap(w*h*4 + DG_MAGIC_4096) 168 out[2] = nx_x11_get_image(conn, pid, 0, 0, w, h, gbuf, w*h*4 + DG_MAGIC_4096) 169 nx_x11_free_pixmap(conn, pid) 170 var mism: i64 = 0 171 var p: i64 = 0 172 while p < w*h { 173 if (gbuf[p*4] & 0xff) != (zbuf[p*4] & 0xff) { mism = mism + 1 } 174 if (gbuf[p*4+1] & 0xff) != (zbuf[p*4+1] & 0xff) { mism = mism + 1 } 175 if (gbuf[p*4+2] & 0xff) != (zbuf[p*4+2] & 0xff) { mism = mism + 1 } 176 p = p + 1 177 } 178 out[3] = mism 179 if out[0] < 0 { return 0 - 1 } 180 if out[2] < 0 { return 0 - 2 } 181 return 0 182} 183 184func main() -> i64 { 185 return 0 186}