feat(input): native gamepad + touch + mouse-position hardware bindings (#51)
Wire the macOS platform side of the #50 device layer, feeding the same state buffers the read APIs consume — no API changes, purely OS glue. - mouse: cocoa.ll reads the live cursor via mouseLocationOutsideOfEventStream, converted to framebuffer pixels and y-flipped, so windowed games get Input.mouse_x/y without injection (W_mx/W_my were never written before). - gamepad: win_pad polls GCController.controllers each frame, packing extended- gamepad buttons (SDL_GameControllerButton order) and thumbsticks (16.16 fixed, Y negated for SDL convention) into in_pad_*. Windowed builds now load GameController via -needed_framework (its classes are reached by name, so a plain -framework link dead-strips it); DCE'd in headless builds. - touch: the view's NSTouch phase handlers snapshot the touching set into in_touch_* (normalizedPosition -> framebuffer pixels). Web platform.js gains zero-fill stubs for win_held/mouse/pad/touch so a windowed wasm build resolves the device-layer imports. New test asserts the windowed link loads GameController. Reseeded; full + selfhost suites green (79 + 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
53bb441f23
commit
d6ca320269
10 changed files with 15159 additions and 14652 deletions
14
changes/input-hardware-bindings.md
Normal file
14
changes/input-hardware-bindings.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
**Native input hardware bindings (#51).** The #50 device layer's platform side
|
||||
is now wired on macOS, feeding the same state buffers the read APIs already use —
|
||||
no API changes. cocoa.ll reads the live cursor position (`mouseLocationOutsideOfEventStream`,
|
||||
converted to framebuffer pixels and y-flipped) so windowed games get
|
||||
`Input.mouse_x/y` without injection; polls GameController.framework each frame
|
||||
(`GCController.controllers` → extended-gamepad buttons/sticks) into
|
||||
`Input.pad_button` / `pad_axis` / `pad_connected`, mapping to SDL_GameControllerButton
|
||||
order; and routes the view's NSTouch phase handlers into `Input.touch_count` /
|
||||
`touch_x/y`. A windowed build now loads GameController (via `-needed_framework`,
|
||||
since its classes are reached by name); the whole binding is DCE'd out of a
|
||||
headless build. `Input.set_pad` / `set_touch` injection still works on every
|
||||
target, so replays, AI, and network-fed input are unchanged.
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
; ============================================================================
|
||||
|
||||
%CGRect = type { double, double, double, double }
|
||||
%NSPoint = type { double, double }
|
||||
|
||||
declare ptr @objc_getClass(ptr)
|
||||
declare ptr @sel_registerName(ptr)
|
||||
|
|
@ -81,6 +82,48 @@ declare i32 @usleep(i32)
|
|||
@.enc_draw = private unnamed_addr constant [36 x i8] c"v@:{CGRect={CGPoint=dd}{CGSize=dd}}\00"
|
||||
@.enc_bool = private unnamed_addr constant [4 x i8] c"c@:\00"
|
||||
|
||||
; #51 — native hardware bindings (mouse position, GameController, NSTouch).
|
||||
@.s_mloc = private unnamed_addr constant [34 x i8] c"mouseLocationOutsideOfEventStream\00"
|
||||
; GameController.framework selectors (poll GCController.controllers each frame)
|
||||
@.c_gc = private unnamed_addr constant [13 x i8] c"GCController\00"
|
||||
@.s_ctrls = private unnamed_addr constant [12 x i8] c"controllers\00"
|
||||
@.s_count = private unnamed_addr constant [6 x i8] c"count\00"
|
||||
@.s_objidx = private unnamed_addr constant [15 x i8] c"objectAtIndex:\00"
|
||||
@.s_extgp = private unnamed_addr constant [16 x i8] c"extendedGamepad\00"
|
||||
@.s_press = private unnamed_addr constant [10 x i8] c"isPressed\00"
|
||||
@.s_value = private unnamed_addr constant [6 x i8] c"value\00"
|
||||
@.s_btnA = private unnamed_addr constant [8 x i8] c"buttonA\00"
|
||||
@.s_btnB = private unnamed_addr constant [8 x i8] c"buttonB\00"
|
||||
@.s_btnX = private unnamed_addr constant [8 x i8] c"buttonX\00"
|
||||
@.s_btnY = private unnamed_addr constant [8 x i8] c"buttonY\00"
|
||||
@.s_lsh = private unnamed_addr constant [13 x i8] c"leftShoulder\00"
|
||||
@.s_rsh = private unnamed_addr constant [14 x i8] c"rightShoulder\00"
|
||||
@.s_ltr = private unnamed_addr constant [12 x i8] c"leftTrigger\00"
|
||||
@.s_rtr = private unnamed_addr constant [13 x i8] c"rightTrigger\00"
|
||||
@.s_bmenu = private unnamed_addr constant [11 x i8] c"buttonMenu\00"
|
||||
@.s_bopt = private unnamed_addr constant [14 x i8] c"buttonOptions\00"
|
||||
@.s_lsb = private unnamed_addr constant [21 x i8] c"leftThumbstickButton\00"
|
||||
@.s_rsb = private unnamed_addr constant [22 x i8] c"rightThumbstickButton\00"
|
||||
@.s_dpad = private unnamed_addr constant [5 x i8] c"dpad\00"
|
||||
@.s_up = private unnamed_addr constant [3 x i8] c"up\00"
|
||||
@.s_down = private unnamed_addr constant [5 x i8] c"down\00"
|
||||
@.s_left = private unnamed_addr constant [5 x i8] c"left\00"
|
||||
@.s_right = private unnamed_addr constant [6 x i8] c"right\00"
|
||||
@.s_lstick = private unnamed_addr constant [15 x i8] c"leftThumbstick\00"
|
||||
@.s_rstick = private unnamed_addr constant [16 x i8] c"rightThumbstick\00"
|
||||
@.s_xaxis = private unnamed_addr constant [6 x i8] c"xAxis\00"
|
||||
@.s_yaxis = private unnamed_addr constant [6 x i8] c"yAxis\00"
|
||||
; NSTouch selectors (trackpad, indirect touches)
|
||||
@.s_setatt = private unnamed_addr constant [22 x i8] c"setAllowedTouchTypes:\00"
|
||||
@.s_tmatch = private unnamed_addr constant [29 x i8] c"touchesMatchingPhase:inView:\00"
|
||||
@.s_normp = private unnamed_addr constant [19 x i8] c"normalizedPosition\00"
|
||||
@.s_allobj = private unnamed_addr constant [11 x i8] c"allObjects\00"
|
||||
@.s_tbegan = private unnamed_addr constant [23 x i8] c"touchesBeganWithEvent:\00"
|
||||
@.s_tmoved = private unnamed_addr constant [23 x i8] c"touchesMovedWithEvent:\00"
|
||||
@.s_tended = private unnamed_addr constant [23 x i8] c"touchesEndedWithEvent:\00"
|
||||
@.s_tcancl = private unnamed_addr constant [27 x i8] c"touchesCancelledWithEvent:\00"
|
||||
@.enc_te = private unnamed_addr constant [5 x i8] c"v@:@\00"
|
||||
|
||||
@W_app = internal global ptr null
|
||||
@W_win = internal global ptr null
|
||||
@W_view = internal global ptr null
|
||||
|
|
@ -97,6 +140,9 @@ declare i32 @usleep(i32)
|
|||
@W_my = internal global i32 0
|
||||
@W_mbtn = internal global i32 0
|
||||
@W_wheel = internal global i32 0
|
||||
; #51 — up to 8 trackpad touch points, [active, x, y] each (framebuffer pixels),
|
||||
; refreshed by the view's touchesBegan/Moved/Ended handlers.
|
||||
@W_touch = internal global [24 x i32] zeroinitializer
|
||||
|
||||
; -drawRect: — blit the framebuffer into the view.
|
||||
; The NSRect argument is ignored, so it never appears in this signature.
|
||||
|
|
@ -175,6 +221,15 @@ entry:
|
|||
%m1 = call i8 @class_addMethod(ptr %cls, ptr %sel_draw, ptr @ludic_drawRect, ptr @.enc_draw)
|
||||
%sel_afr = call ptr @sel_registerName(ptr @.s_afr)
|
||||
%m2 = call i8 @class_addMethod(ptr %cls, ptr %sel_afr, ptr @ludic_yes, ptr @.enc_bool)
|
||||
; #51 — the four NSTouch phase handlers, all routed to @ludic_touches.
|
||||
%sel_tb = call ptr @sel_registerName(ptr @.s_tbegan)
|
||||
%mt1 = call i8 @class_addMethod(ptr %cls, ptr %sel_tb, ptr @ludic_touches, ptr @.enc_te)
|
||||
%sel_tm = call ptr @sel_registerName(ptr @.s_tmoved)
|
||||
%mt2 = call i8 @class_addMethod(ptr %cls, ptr %sel_tm, ptr @ludic_touches, ptr @.enc_te)
|
||||
%sel_te = call ptr @sel_registerName(ptr @.s_tended)
|
||||
%mt3 = call i8 @class_addMethod(ptr %cls, ptr %sel_te, ptr @ludic_touches, ptr @.enc_te)
|
||||
%sel_tc = call ptr @sel_registerName(ptr @.s_tcancl)
|
||||
%mt4 = call i8 @class_addMethod(ptr %cls, ptr %sel_tc, ptr @ludic_touches, ptr @.enc_te)
|
||||
call void @objc_registerClassPair(ptr %cls)
|
||||
|
||||
%wd = sitofp i32 %w to double
|
||||
|
|
@ -205,6 +260,9 @@ entry:
|
|||
%sel_initf = call ptr @sel_registerName(ptr @.s_initf)
|
||||
%view = call ptr (ptr, ptr, %CGRect) @objc_msgSend(ptr %v0, ptr %sel_initf, %CGRect %rect)
|
||||
store ptr %view, ptr @W_view
|
||||
; #51 — accept indirect (trackpad) touches. NSTouchTypeMaskIndirect = 1<<0.
|
||||
%sel_att = call ptr @sel_registerName(ptr @.s_setatt)
|
||||
%att = call ptr (ptr, ptr, i64) @objc_msgSend(ptr %view, ptr %sel_att, i64 1)
|
||||
%sel_setcv = call ptr @sel_registerName(ptr @.s_setcv)
|
||||
%a3 = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_setcv, ptr %view)
|
||||
%sel_center = call ptr @sel_registerName(ptr @.s_center)
|
||||
|
|
@ -479,6 +537,30 @@ ret:
|
|||
; window points; buttons and wheel come from the event pump above.
|
||||
define void @win_mouse(ptr %out) {
|
||||
entry:
|
||||
; #51 — read the live cursor position from the window (points, origin
|
||||
; bottom-left) and convert to framebuffer pixels, y-flipped so row 0 is the
|
||||
; top, matching the framebuffer the game draws into.
|
||||
%win = load ptr, ptr @W_win
|
||||
%nowin = icmp eq ptr %win, null
|
||||
br i1 %nowin, label %emit, label %qpos
|
||||
qpos:
|
||||
%sel_ml = call ptr @sel_registerName(ptr @.s_mloc)
|
||||
%pt = call %NSPoint (ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_ml)
|
||||
%px = extractvalue %NSPoint %pt, 0
|
||||
%py = extractvalue %NSPoint %pt, 1
|
||||
%sc = load i32, ptr @W_scale
|
||||
%scd = sitofp i32 %sc to double
|
||||
%fbh = load i32, ptr @W_fbh
|
||||
%fbhd = sitofp i32 %fbh to double
|
||||
%fx = fdiv double %px, %scd
|
||||
%fyy = fdiv double %py, %scd
|
||||
%fy = fsub double %fbhd, %fyy
|
||||
%ix = fptosi double %fx to i32
|
||||
%iy = fptosi double %fy to i32
|
||||
store i32 %ix, ptr @W_mx
|
||||
store i32 %iy, ptr @W_my
|
||||
br label %emit
|
||||
emit:
|
||||
%mx = load i32, ptr @W_mx
|
||||
%p0 = getelementptr i32, ptr %out, i32 0
|
||||
store i32 %mx, ptr %p0
|
||||
|
|
@ -494,3 +576,296 @@ entry:
|
|||
store i32 0, ptr @W_wheel
|
||||
ret void
|
||||
}
|
||||
|
||||
; ============================================================================
|
||||
; #51 — gamepad, via GameController.framework.
|
||||
;
|
||||
; Each frame win_pad() walks [GCController controllers] and, for every connected
|
||||
; controller that exposes an -extendedGamepad, packs its state into the caller's
|
||||
; buffer: six words per pad — [connected, button-mask, lx, ly, rx, ly] — for up
|
||||
; to four pads. Axes are 16.16 fixed (float value * 65536); the stick Y is
|
||||
; negated so "down" is positive, matching the SDL axis convention the device
|
||||
; layer maps onto. The button mask follows SDL_GameControllerButton order:
|
||||
; 0 A 1 B 2 X 3 Y 4 Back 5 Guide 6 Start 7 LStick 8 RStick
|
||||
; 9 LShoulder 10 RShoulder 11 DPadUp 12 DPadDown 13 DPadLeft 14 DPadRight
|
||||
; 15 LTrigger 16 RTrigger
|
||||
; nil property messaging returns 0, so a controller missing a given button
|
||||
; simply reports it unpressed. This whole binding is DCE'd out of a headless
|
||||
; build (win_pad is only reached on the is_windowed() branch).
|
||||
; ============================================================================
|
||||
|
||||
; Is the button property named %name pressed on extended-gamepad %egp? 0 or 1.
|
||||
define i32 @gc_pressed(ptr %egp, ptr %name) {
|
||||
entry:
|
||||
%sel = call ptr @sel_registerName(ptr %name)
|
||||
%btn = call ptr (ptr, ptr) @objc_msgSend(ptr %egp, ptr %sel)
|
||||
%nil = icmp eq ptr %btn, null
|
||||
br i1 %nil, label %zero, label %ask
|
||||
ask:
|
||||
%selp = call ptr @sel_registerName(ptr @.s_press)
|
||||
%b = call i8 (ptr, ptr) @objc_msgSend(ptr %btn, ptr %selp)
|
||||
%bi = zext i8 %b to i32
|
||||
%r = and i32 %bi, 1
|
||||
ret i32 %r
|
||||
zero:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Two-level version: %grp names a sub-object (e.g. "dpad"), %dir the button on
|
||||
; it (e.g. "up").
|
||||
define i32 @gc_pressed2(ptr %egp, ptr %grp, ptr %dir) {
|
||||
entry:
|
||||
%selg = call ptr @sel_registerName(ptr %grp)
|
||||
%g = call ptr (ptr, ptr) @objc_msgSend(ptr %egp, ptr %selg)
|
||||
%nil = icmp eq ptr %g, null
|
||||
br i1 %nil, label %zero, label %ask
|
||||
ask:
|
||||
%r = call i32 @gc_pressed(ptr %g, ptr %dir)
|
||||
ret i32 %r
|
||||
zero:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; The value of axis %axis on stick %stick, as 16.16 fixed. 0 if either is nil.
|
||||
define i32 @gc_axis(ptr %egp, ptr %stick, ptr %axis) {
|
||||
entry:
|
||||
%sels = call ptr @sel_registerName(ptr %stick)
|
||||
%st = call ptr (ptr, ptr) @objc_msgSend(ptr %egp, ptr %sels)
|
||||
%n1 = icmp eq ptr %st, null
|
||||
br i1 %n1, label %zero, label %a2
|
||||
a2:
|
||||
%sela = call ptr @sel_registerName(ptr %axis)
|
||||
%ax = call ptr (ptr, ptr) @objc_msgSend(ptr %st, ptr %sela)
|
||||
%n2 = icmp eq ptr %ax, null
|
||||
br i1 %n2, label %zero, label %read
|
||||
read:
|
||||
%selv = call ptr @sel_registerName(ptr @.s_value)
|
||||
%f = call float (ptr, ptr) @objc_msgSend(ptr %ax, ptr %selv)
|
||||
%scaled = fmul float %f, 6.5536e+04
|
||||
%i = fptosi float %scaled to i32
|
||||
ret i32 %i
|
||||
zero:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; The full SDL-ordered button mask for one extended gamepad.
|
||||
define i32 @gc_mask(ptr %egp) {
|
||||
entry:
|
||||
%a = call i32 @gc_pressed(ptr %egp, ptr @.s_btnA)
|
||||
%b = call i32 @gc_pressed(ptr %egp, ptr @.s_btnB)
|
||||
%x = call i32 @gc_pressed(ptr %egp, ptr @.s_btnX)
|
||||
%y = call i32 @gc_pressed(ptr %egp, ptr @.s_btnY)
|
||||
%bk = call i32 @gc_pressed(ptr %egp, ptr @.s_bopt)
|
||||
%st = call i32 @gc_pressed(ptr %egp, ptr @.s_bmenu)
|
||||
%ls = call i32 @gc_pressed(ptr %egp, ptr @.s_lsb)
|
||||
%rs = call i32 @gc_pressed(ptr %egp, ptr @.s_rsb)
|
||||
%lsh = call i32 @gc_pressed(ptr %egp, ptr @.s_lsh)
|
||||
%rsh = call i32 @gc_pressed(ptr %egp, ptr @.s_rsh)
|
||||
%du = call i32 @gc_pressed2(ptr %egp, ptr @.s_dpad, ptr @.s_up)
|
||||
%dd = call i32 @gc_pressed2(ptr %egp, ptr @.s_dpad, ptr @.s_down)
|
||||
%dl = call i32 @gc_pressed2(ptr %egp, ptr @.s_dpad, ptr @.s_left)
|
||||
%dr = call i32 @gc_pressed2(ptr %egp, ptr @.s_dpad, ptr @.s_right)
|
||||
%lt = call i32 @gc_pressed(ptr %egp, ptr @.s_ltr)
|
||||
%rt = call i32 @gc_pressed(ptr %egp, ptr @.s_rtr)
|
||||
%s1 = shl i32 %b, 1
|
||||
%s2 = shl i32 %x, 2
|
||||
%s3 = shl i32 %y, 3
|
||||
%s4 = shl i32 %bk, 4
|
||||
%s6 = shl i32 %st, 6
|
||||
%s7 = shl i32 %ls, 7
|
||||
%s8 = shl i32 %rs, 8
|
||||
%s9 = shl i32 %lsh, 9
|
||||
%s10 = shl i32 %rsh, 10
|
||||
%s11 = shl i32 %du, 11
|
||||
%s12 = shl i32 %dd, 12
|
||||
%s13 = shl i32 %dl, 13
|
||||
%s14 = shl i32 %dr, 14
|
||||
%s15 = shl i32 %lt, 15
|
||||
%s16 = shl i32 %rt, 16
|
||||
%o0 = or i32 %a, %s1
|
||||
%o1 = or i32 %o0, %s2
|
||||
%o2 = or i32 %o1, %s3
|
||||
%o3 = or i32 %o2, %s4
|
||||
%o4 = or i32 %o3, %s6
|
||||
%o5 = or i32 %o4, %s7
|
||||
%o6 = or i32 %o5, %s8
|
||||
%o7 = or i32 %o6, %s9
|
||||
%o8 = or i32 %o7, %s10
|
||||
%o9 = or i32 %o8, %s11
|
||||
%o10 = or i32 %o9, %s12
|
||||
%o11 = or i32 %o10, %s13
|
||||
%o12 = or i32 %o11, %s14
|
||||
%o13 = or i32 %o12, %s15
|
||||
%o14 = or i32 %o13, %s16
|
||||
ret i32 %o14
|
||||
}
|
||||
|
||||
; Fill the caller's 24-word buffer: [connected, mask, lx, ly, rx, ry] per pad.
|
||||
define void @win_pad(ptr %out) {
|
||||
entry:
|
||||
br label %clr
|
||||
clr:
|
||||
%ci = phi i32 [ 0, %entry ], [ %cn, %clrb ]
|
||||
%cd = icmp sge i32 %ci, 24
|
||||
br i1 %cd, label %start, label %clrb
|
||||
clrb:
|
||||
%cp = getelementptr i32, ptr %out, i32 %ci
|
||||
store i32 0, ptr %cp
|
||||
%cn = add i32 %ci, 1
|
||||
br label %clr
|
||||
start:
|
||||
%gccls = call ptr @objc_getClass(ptr @.c_gc)
|
||||
%nocls = icmp eq ptr %gccls, null
|
||||
br i1 %nocls, label %done, label %getarr
|
||||
getarr:
|
||||
%selc = call ptr @sel_registerName(ptr @.s_ctrls)
|
||||
%arr = call ptr (ptr, ptr) @objc_msgSend(ptr %gccls, ptr %selc)
|
||||
%noarr = icmp eq ptr %arr, null
|
||||
br i1 %noarr, label %done, label %count
|
||||
count:
|
||||
%seln = call ptr @sel_registerName(ptr @.s_count)
|
||||
%n64 = call i64 (ptr, ptr) @objc_msgSend(ptr %arr, ptr %seln)
|
||||
%n = trunc i64 %n64 to i32
|
||||
br label %loop
|
||||
loop:
|
||||
%p = phi i32 [ 0, %count ], [ %pn, %cont ]
|
||||
%lt4 = icmp slt i32 %p, 4
|
||||
%ltn = icmp slt i32 %p, %n
|
||||
%ok = and i1 %lt4, %ltn
|
||||
br i1 %ok, label %body, label %done
|
||||
body:
|
||||
%seloi = call ptr @sel_registerName(ptr @.s_objidx)
|
||||
%p64 = sext i32 %p to i64
|
||||
%ctl = call ptr (ptr, ptr, i64) @objc_msgSend(ptr %arr, ptr %seloi, i64 %p64)
|
||||
%selg = call ptr @sel_registerName(ptr @.s_extgp)
|
||||
%egp = call ptr (ptr, ptr) @objc_msgSend(ptr %ctl, ptr %selg)
|
||||
%noegp = icmp eq ptr %egp, null
|
||||
br i1 %noegp, label %cont, label %pack
|
||||
pack:
|
||||
%base = mul i32 %p, 6
|
||||
%b0 = getelementptr i32, ptr %out, i32 %base
|
||||
store i32 1, ptr %b0
|
||||
%mask = call i32 @gc_mask(ptr %egp)
|
||||
%i1 = add i32 %base, 1
|
||||
%b1 = getelementptr i32, ptr %out, i32 %i1
|
||||
store i32 %mask, ptr %b1
|
||||
%lx = call i32 @gc_axis(ptr %egp, ptr @.s_lstick, ptr @.s_xaxis)
|
||||
%i2 = add i32 %base, 2
|
||||
%b2 = getelementptr i32, ptr %out, i32 %i2
|
||||
store i32 %lx, ptr %b2
|
||||
%lyr = call i32 @gc_axis(ptr %egp, ptr @.s_lstick, ptr @.s_yaxis)
|
||||
%ly = sub i32 0, %lyr
|
||||
%i3 = add i32 %base, 3
|
||||
%b3 = getelementptr i32, ptr %out, i32 %i3
|
||||
store i32 %ly, ptr %b3
|
||||
%rx = call i32 @gc_axis(ptr %egp, ptr @.s_rstick, ptr @.s_xaxis)
|
||||
%i4 = add i32 %base, 4
|
||||
%b4 = getelementptr i32, ptr %out, i32 %i4
|
||||
store i32 %rx, ptr %b4
|
||||
%ryr = call i32 @gc_axis(ptr %egp, ptr @.s_rstick, ptr @.s_yaxis)
|
||||
%ry = sub i32 0, %ryr
|
||||
%i5 = add i32 %base, 5
|
||||
%b5 = getelementptr i32, ptr %out, i32 %i5
|
||||
store i32 %ry, ptr %b5
|
||||
br label %cont
|
||||
cont:
|
||||
%pn = add i32 %p, 1
|
||||
br label %loop
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
; ============================================================================
|
||||
; #51 — touch, via NSTouch (trackpad / indirect touches).
|
||||
;
|
||||
; The view's touchesBegan/Moved/Ended/Cancelled handlers all funnel through
|
||||
; @ludic_touches, which resnapshots the set of touches currently down into
|
||||
; @W_touch as [active, x, y] per point (framebuffer pixels; normalizedPosition
|
||||
; is 0..1, origin bottom-left, so y is flipped). win_touch() copies that out.
|
||||
; ============================================================================
|
||||
define internal void @ludic_touches(ptr %self, ptr %sel, ptr %event) {
|
||||
entry:
|
||||
br label %clr
|
||||
clr:
|
||||
%ci = phi i32 [ 0, %entry ], [ %cn, %clrb ]
|
||||
%cd = icmp sge i32 %ci, 24
|
||||
br i1 %cd, label %get, label %clrb
|
||||
clrb:
|
||||
%cp = getelementptr [24 x i32], ptr @W_touch, i32 0, i32 %ci
|
||||
store i32 0, ptr %cp
|
||||
%cn = add i32 %ci, 1
|
||||
br label %clr
|
||||
get:
|
||||
%selm = call ptr @sel_registerName(ptr @.s_tmatch)
|
||||
; NSTouchPhaseTouching = Began|Moved|Stationary = 1|2|4 = 7
|
||||
%set = call ptr (ptr, ptr, i64, ptr) @objc_msgSend(ptr %event, ptr %selm, i64 7, ptr %self)
|
||||
%noset = icmp eq ptr %set, null
|
||||
br i1 %noset, label %ret, label %toarr
|
||||
toarr:
|
||||
%sela = call ptr @sel_registerName(ptr @.s_allobj)
|
||||
%arr = call ptr (ptr, ptr) @objc_msgSend(ptr %set, ptr %sela)
|
||||
%noarr = icmp eq ptr %arr, null
|
||||
br i1 %noarr, label %ret, label %count
|
||||
count:
|
||||
%seln = call ptr @sel_registerName(ptr @.s_count)
|
||||
%n64 = call i64 (ptr, ptr) @objc_msgSend(ptr %arr, ptr %seln)
|
||||
%n = trunc i64 %n64 to i32
|
||||
br label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %count ], [ %ni, %cont ]
|
||||
%lt8 = icmp slt i32 %i, 8
|
||||
%ltn = icmp slt i32 %i, %n
|
||||
%ok = and i1 %lt8, %ltn
|
||||
br i1 %ok, label %body, label %ret
|
||||
body:
|
||||
%seloi = call ptr @sel_registerName(ptr @.s_objidx)
|
||||
%i64v = sext i32 %i to i64
|
||||
%t = call ptr (ptr, ptr, i64) @objc_msgSend(ptr %arr, ptr %seloi, i64 %i64v)
|
||||
%selnp = call ptr @sel_registerName(ptr @.s_normp)
|
||||
%np = call %NSPoint (ptr, ptr) @objc_msgSend(ptr %t, ptr %selnp)
|
||||
%nx = extractvalue %NSPoint %np, 0
|
||||
%ny = extractvalue %NSPoint %np, 1
|
||||
%fbw = load i32, ptr @W_fbw
|
||||
%fbh = load i32, ptr @W_fbh
|
||||
%fbwd = sitofp i32 %fbw to double
|
||||
%fbhd = sitofp i32 %fbh to double
|
||||
%xd = fmul double %nx, %fbwd
|
||||
%ny1 = fsub double 1.0, %ny
|
||||
%yd = fmul double %ny1, %fbhd
|
||||
%xi = fptosi double %xd to i32
|
||||
%yi = fptosi double %yd to i32
|
||||
%base = mul i32 %i, 3
|
||||
%o0 = getelementptr [24 x i32], ptr @W_touch, i32 0, i32 %base
|
||||
store i32 1, ptr %o0
|
||||
%j1 = add i32 %base, 1
|
||||
%o1 = getelementptr [24 x i32], ptr @W_touch, i32 0, i32 %j1
|
||||
store i32 %xi, ptr %o1
|
||||
%j2 = add i32 %base, 2
|
||||
%o2 = getelementptr [24 x i32], ptr @W_touch, i32 0, i32 %j2
|
||||
store i32 %yi, ptr %o2
|
||||
br label %cont
|
||||
cont:
|
||||
%ni = add i32 %i, 1
|
||||
br label %loop
|
||||
ret:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Copy the 24-word touch snapshot into the caller's buffer.
|
||||
define void @win_touch(ptr %out) {
|
||||
entry:
|
||||
br label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %entry ], [ %ni, %body ]
|
||||
%done = icmp sge i32 %i, 24
|
||||
br i1 %done, label %ret, label %body
|
||||
body:
|
||||
%sp = getelementptr [24 x i32], ptr @W_touch, i32 0, i32 %i
|
||||
%v = load i32, ptr %sp
|
||||
%dp = getelementptr i32, ptr %out, i32 %i
|
||||
store i32 %v, ptr %dp
|
||||
%ni = add i32 %i, 1
|
||||
br label %loop
|
||||
ret:
|
||||
ret void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,6 +239,27 @@ function input_device_commit(k: int, replaying: int) -> void {
|
|||
let mbuf = words(4) # [x, y, button-mask, wheel]
|
||||
win_mouse(mbuf)
|
||||
in_mx = mbuf[0]; in_my = mbuf[1]; in_mbtn = mbuf[2]; in_wheel = mbuf[3]
|
||||
# #51 — feed the platform gamepad + touch state into the same buffers the
|
||||
# read APIs use. Each is windowed-only glue (win_pad / win_touch are DCE'd
|
||||
# in a headless build); on hardware they overwrite the injected state.
|
||||
let pbuf = words(IN_PADS * 6) # [conn, mask, lx, ly, rx, ry]/pad
|
||||
win_pad(pbuf)
|
||||
var pi = 0
|
||||
while pi < IN_PADS {
|
||||
let pb = pi * 6
|
||||
input_set_pad(pi, pbuf[pb] != 0, pbuf[pb + 1],
|
||||
as_fixed(pbuf[pb + 2]), as_fixed(pbuf[pb + 3]),
|
||||
as_fixed(pbuf[pb + 4]), as_fixed(pbuf[pb + 5]))
|
||||
pi = pi + 1
|
||||
}
|
||||
let tbuf = words(IN_TOUCH * 3) # [active, x, y]/point
|
||||
win_touch(tbuf)
|
||||
var ti = 0
|
||||
while ti < IN_TOUCH {
|
||||
let tb = ti * 3
|
||||
input_set_touch(ti, tbuf[tb + 1], tbuf[tb + 2], tbuf[tb] != 0)
|
||||
ti = ti + 1
|
||||
}
|
||||
} else {
|
||||
in_set_clear(in_dev)
|
||||
if k > 0 { in_bit_set(in_dev, k, true) }
|
||||
|
|
|
|||
|
|
@ -296,6 +296,13 @@ async function start({ wasm, canvas, manifest = null, files = {}, onExit = null,
|
|||
},
|
||||
win_running: () => host.running,
|
||||
win_close: () => { host.running = 0; },
|
||||
// The #50/#51 device-state fills. The browser has no gamepad/touch hardware
|
||||
// wired here yet, so these zero the caller's buffer; keyboard still flows
|
||||
// through win_poll above, and Input.set_* injection works on every target.
|
||||
win_held: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 8; i++) host.u32[b + i] = 0; },
|
||||
win_mouse: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 4; i++) host.u32[b + i] = 0; },
|
||||
win_pad: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 24; i++) host.u32[b + i] = 0; },
|
||||
win_touch: (p) => { host.sync(); const b = p >>> 2; for (let i = 0; i < 24; i++) host.u32[b + i] = 0; },
|
||||
};
|
||||
|
||||
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasm), { env });
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ function emit_header() -> void {
|
|||
emith("declare void @win_close()\n")
|
||||
emith("declare void @win_held(ptr)\n")
|
||||
emith("declare void @win_mouse(ptr)\n")
|
||||
emith("declare void @win_pad(ptr)\n")
|
||||
emith("declare void @win_touch(ptr)\n")
|
||||
emith("@__stderrp = external global ptr\n")
|
||||
emith("@__stdoutp = external global ptr\n")
|
||||
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ function is_intrinsic2(name: pointer) -> bool {
|
|||
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
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -55,5 +56,9 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
|||
# 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") }
|
||||
return val("0", "void")
|
||||
}
|
||||
|
|
|
|||
29374
selfhost/ludicc.seed.ll
29374
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -160,7 +160,11 @@ entry {
|
|||
var cmd = `{cc} -O2 -Wno-override-module {ll}`
|
||||
if g_windowed {
|
||||
let cocoa = path_join(home, "runtime/native/cocoa.ll")
|
||||
cmd = `{cmd} {cocoa} -framework Cocoa -Wl,-rpath,@loader_path`
|
||||
# GameController holds no symbol cocoa.ll references directly (its classes are
|
||||
# reached by name through objc_getClass), so a plain -framework link gets
|
||||
# dead-stripped; -needed_framework forces the load command so the class is
|
||||
# registered and #51's gamepad polling can see it.
|
||||
cmd = `{cmd} {cocoa} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-rpath,@loader_path`
|
||||
}
|
||||
cmd = `{cmd} -o {out}`
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ function cmd_app() -> int {
|
|||
let ll = `{outbin}.ll`
|
||||
print(`ludicc: {src} -> {outbin} (Ludic-written compiler -> LLVM IR -> windowed binary)`)
|
||||
if not shq(`bin/ludicc --windowed {src} > {ll}`) { return 1 }
|
||||
if not shq(`{cc()} -O2 {ll} runtime/native/cocoa.ll -framework Cocoa -Wl,-rpath,@loader_path -o {outbin}`) { return 1 }
|
||||
if not shq(`{cc()} -O2 {ll} runtime/native/cocoa.ll -framework Cocoa -Wl,-needed_framework,GameController -Wl,-rpath,@loader_path -o {outbin}`) { return 1 }
|
||||
if not save { run(`rm -f {ll}`) }
|
||||
print(`done. run: ./{outbin} (from the repo root, so assets/ resolves)`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -288,6 +288,11 @@ function cmd_test() -> int {
|
|||
if shq("LUDIC_HOME=. bin/ludicc examples/games/snake.ludic -o /tmp/x_cli_snake > /tmp/x_cli.out 2>&1") and shq("file /tmp/x_cli_snake 2>/dev/null | grep -q Mach-O") {
|
||||
ok("ludicc app.ludic -o bin -> native executable")
|
||||
} else { bad2("ludicc -o", capture_line("tail -1 /tmp/x_cli.out")) }
|
||||
# #51 — a windowed build must load GameController so the native gamepad
|
||||
# polling (win_pad, reached on the is_windowed branch) can see controllers.
|
||||
if shq("otool -L /tmp/x_cli_snake 2>/dev/null | grep -q GameController") {
|
||||
ok("windowed link loads GameController (#51 gamepad binding)")
|
||||
} else { bad("windowed binary is missing the GameController load command") }
|
||||
} else {
|
||||
if shq("LUDIC_HOME=. bin/ludicc --headless examples/games/snake.ludic -o /tmp/x_cli_snake > /tmp/x_cli.out 2>&1") and shq("test -x /tmp/x_cli_snake") {
|
||||
ok("ludicc app.ludic -o bin -> native executable (headless)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue