A windowed action game can hide the OS cursor and lock/confine the mouse to the window. Input.cursor_mode(mode): 0 normal, 1 hidden (draw your own reticle), 2 locked (hidden + dissociated — the mouse feeds relative motion via Input.mouse_dx/dy and Input.mouse_x/y is a clamped virtual cursor, FPS/twin-stick aim), 3 confined (dissociated but visible; the mouse can't leave the window). The platform auto-releases (shows + reconnects) while the window is not key (Cmd-Tab) and on close, so the cursor is never left captured. Headless it is a no-op (DCE'd). Native macOS impl in cocoa.ll: [NSCursor hide]/[unhide] (ref-counted, toggled only on change so the count stays balanced across focus changes), CGAssociateMouseAndMouseCursorPosition, and CGGetLastMouseDelta for the relative virtual cursor, behind a new win_cursor_mode intrinsic. Windowed-only behaviour (not in the headless golden suite); example compiles headless and links windowed. Full suite 115/0, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
7.5 KiB
Text
93 lines
7.5 KiB
Text
# emit_intrin2.ludic — the rest of the low-level intrinsics: raw memory, fixed
|
|
# memory access, pointer arithmetic, stdio, time, and the windowing hooks.
|
|
# Matches compiler/back/ir_intrin.c. Reached from emit_intrinsic's fall-through.
|
|
|
|
var g_windowed: bool = false # headless by default (games read stdin / dump PPM)
|
|
|
|
function is_intrinsic2(name: pointer) -> bool {
|
|
if (name == "free") or (name == "fill") { return true }
|
|
if (name == "offset") or (name == "read_char") { return true }
|
|
if (name == "as_fixed") or (name == "as_int") { return true }
|
|
if (name == "is_windowed") or (name == "game_title") { return true }
|
|
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
|
|
if (name == "win_running") or (name == "win_close") { return true }
|
|
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
|
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
|
if (name == "win_cursor_mode") { return true } # #89 cursor capture
|
|
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
|
if (name == "snd_playing") or (name == "snd_set_volume") or (name == "snd_set_rate") { return true }
|
|
if (name == "hs_req_new") or (name == "hs_req_header") or (name == "hs_req_body") { return true } # #6 HTTP
|
|
if (name == "hs_send") or (name == "hs_done") or (name == "hs_status") or (name == "hs_body") { return true }
|
|
if (name == "hs_blen") or (name == "hs_header") or (name == "hs_free") { return true }
|
|
return false
|
|
}
|
|
|
|
function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
|
if (name == "free") {
|
|
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
|
|
}
|
|
if (name == "fill") {
|
|
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
|
|
let w = emit_bind(`zext i32 {n} to i64`)
|
|
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "offset") {
|
|
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
|
let g = nreg()
|
|
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
|
return val(g, "pointer")
|
|
}
|
|
if (name == "read_char") { return val(emit_bind("call i32 @getchar()"), "int") }
|
|
if (name == "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
|
|
if (name == "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
|
|
if (name == "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
|
|
if (name == "game_title") { return val("@.gametitle", "string") }
|
|
# windowing hooks — declared external; only reached on the is_windowed() branch
|
|
if (name == "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
|
|
if (name == "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
|
|
if (name == "win_open") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
|
|
emit(" call void @win_open(i32 "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", ptr "); emit(d); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "win_present") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2)
|
|
emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
|
|
# #50 — fill a caller buffer with the platform device state (windowed only,
|
|
# DCE'd headless). win_held: an 8-word held-key bitset; win_mouse: [x, y,
|
|
# button-mask, wheel-delta].
|
|
if (name == "win_held") { let a = arg_code(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "win_mouse") { let a = arg_code(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# #51 — win_pad: 6 words/pad [connected, mask, lx, ly, rx, ry]; win_touch: 3
|
|
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
|
if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "win_touch") { let a = arg_code(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# #89 — set the OS cursor capture mode (windowed only; DCE'd headless).
|
|
if (name == "win_cursor_mode") { let a = arg_code(e, 0); emit(" call void @win_cursor_mode(i32 "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# #22 audio backend — AVAudioPlayer via audio.ll; windowed-only, DCE'd headless.
|
|
if (name == "snd_load") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @snd_load(ptr {a})`), "pointer") }
|
|
if (name == "snd_play") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
|
|
emit(" call void @snd_play(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", i32 "); emit(d); emit(")\n"); return val("0", "void")
|
|
}
|
|
if (name == "snd_stop") { let a = arg_code(e, 0); emit(" call void @snd_stop(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "snd_playing") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @snd_playing(ptr {a})`), "int") }
|
|
if (name == "snd_set_volume") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @snd_set_volume(ptr "); emit(a); emit(", i32 "); emit(b); emit(")\n"); return val("0", "void") }
|
|
if (name == "snd_set_rate") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @snd_set_rate(ptr "); emit(a); emit(", i32 "); emit(b); emit(")\n"); return val("0", "void") }
|
|
# #6 HTTP transport — NSURLConnection on a background thread (http.ll).
|
|
if (name == "hs_req_new") { let a = arg_code(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call ptr @hs_req_new(ptr {a}, ptr {b})`), "pointer") }
|
|
if (name == "hs_req_header") { let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); emit(" call void @hs_req_header(ptr "); emit(a); emit(", ptr "); emit(b); emit(", ptr "); emit(c); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_req_body") { let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); emit(" call void @hs_req_body(ptr "); emit(a); emit(", ptr "); emit(b); emit(", i32 "); emit(c); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_send") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @hs_send(i32 "); emit(a); emit(", ptr "); emit(b); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_done") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_done(i32 {a})`), "int") }
|
|
if (name == "hs_status") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_status(i32 {a})`), "int") }
|
|
if (name == "hs_body") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @hs_body(i32 {a})`), "pointer") }
|
|
if (name == "hs_blen") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_blen(i32 {a})`), "int") }
|
|
if (name == "hs_header") { let a = arg_code(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call ptr @hs_header(i32 {a}, ptr {b})`), "pointer") }
|
|
if (name == "hs_free") { let a = arg_code(e, 0); emit(" call void @hs_free(i32 "); emit(a); emit(")\n"); return val("0", "void") }
|
|
return val("0", "void")
|
|
}
|