ludic/runtime/native/cocoa.ll
Orkuncakilkaya ad548840c7
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 32s
ci / build-and-test (push) Successful in 2m49s
commit-lint / conventional-commits (push) Successful in 5s
docs / build-and-deploy (push) Successful in 30s
feat(engine): #90 atlas-aware Sprite component, #91 become from listeners, 0.3.x ergonomics batch
Closes the two open issues and lands the pending unreleased batch:

- #90: `Sprite { atlas: 1 }` routes esys_sprite through atlas_draw_ex
  (scale/flip/tint), so cell / cell_span / strip ids of any size draw
  through the engine sprite-render system. examples/library/sprite_atlas
  is the pixel-readback regression.
- #91: `become` from an @On(Event) listener / global handler / plain
  function no longer segfaults the compiler; it emits @L_scene_leave()
  (a dispatch on the live scene id) so the leaving scene's on-exit runs.
  UI_* handles are readable from any code (widget table built on first
  use). examples/library/scene_menus covers it.
- fix: a windowed `ludicc -o` build that reaches the audio runtime only
  through the atlas/Assets preload import now links audio.ll +
  AVFoundation (the audio backend link was gated on a game-level
  Audio.* call, so any windowed game declaring Sprite failed to link).
- the hand-written "Unreleased" CHANGELOG section is converted to
  changesets under changes/ so `x release` generates it.
- plus the batch: engine-driven retained UI + UiClicked event, Overlay
  phase, TileSkin tilemap-render system, Key.* constants, Font/Ui/File
  namespaces, Sprite.strip, prefabs, managers, countdown fields,
  enum-typed machines, layer @Queries, ludic.prefs / ludic.dungeon
  packages, Ai.seek pathing, Solids.solid2, cursor confine (mode 3)
  fix, shooter centre-aim fix, reserved-word function diagnostic.

Verified: x test (124/124), x test-tools, check-impl, check-vocabulary,
check-docs, docs-gen + docs-check, bootstrap-cfree (seed is a fixpoint).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 01:36:08 +03:00

1109 lines
44 KiB
LLVM

; ============================================================================
; cocoa.ll — the macOS window, written in LLVM IR.
;
; This is what replaced plat_cocoa.m. There is no Objective-C source and no C
; source in a Ludic build: the window is opened by talking to the Objective-C
; runtime through its C ABI (objc_getClass / sel_registerName / objc_msgSend)
; and to Quartz through CoreGraphics, exactly as a compiled .m file would, only
; written at the level ludicc itself emits. ludicc splices this module in when
; it links a windowed macOS binary.
;
; The one piece that genuinely needs a class is the view: AppKit paints through
; -drawRect:, so a class is built at runtime with objc_allocateClassPair and
; @ludic_drawRect is installed as its IMP. On arm64 the unused NSRect argument
; simply stays in registers we never read, so the IMP needs no struct in its
; signature.
;
; Exposed to Ludic (through the win_* intrinsics):
; win_open(w, h, scale, title) win_poll() -> key win_present(fb, w, h)
; win_running() -> bool win_close()
; ============================================================================
%CGRect = type { double, double, double, double }
%NSPoint = type { double, double }
declare ptr @objc_getClass(ptr)
declare ptr @sel_registerName(ptr)
declare ptr @objc_msgSend(ptr, ptr, ...)
declare ptr @objc_allocateClassPair(ptr, ptr, i64)
declare void @objc_registerClassPair(ptr)
declare i8 @class_addMethod(ptr, ptr, ptr, ptr)
declare ptr @CGColorSpaceCreateDeviceRGB()
declare ptr @CGBitmapContextCreate(ptr, i64, i64, i64, i64, ptr, i32)
declare ptr @CGBitmapContextCreateImage(ptr)
declare void @CGContextDrawImage(ptr, %CGRect, ptr)
declare void @CGContextSetInterpolationQuality(ptr, i32)
declare void @CGContextTranslateCTM(ptr, double, double)
declare void @CGContextScaleCTM(ptr, double, double)
declare void @CGContextSaveGState(ptr)
declare void @CGContextRestoreGState(ptr)
declare void @CGImageRelease(ptr)
declare void @CGContextRelease(ptr)
declare void @CGColorSpaceRelease(ptr)
declare i32 @usleep(i32)
; #89 — cursor capture: NSCursor hide/unhide (ref-counted) + the CoreGraphics
; mouse-association / relative-delta calls.
declare i32 @CGAssociateMouseAndMouseCursorPosition(i32)
declare void @CGGetLastMouseDelta(ptr, ptr)
declare i32 @CGWarpMouseCursorPosition(%NSPoint)
@.c_app = private unnamed_addr constant [14 x i8] c"NSApplication\00"
@.c_win = private unnamed_addr constant [9 x i8] c"NSWindow\00"
@.c_str = private unnamed_addr constant [9 x i8] c"NSString\00"
@.c_date = private unnamed_addr constant [7 x i8] c"NSDate\00"
@.c_view = private unnamed_addr constant [7 x i8] c"NSView\00"
@.c_gctx = private unnamed_addr constant [18 x i8] c"NSGraphicsContext\00"
@.c_ludic = private unnamed_addr constant [10 x i8] c"LudicView\00"
@.c_cursor = private unnamed_addr constant [9 x i8] c"NSCursor\00"
@.c_screen = private unnamed_addr constant [9 x i8] c"NSScreen\00"
@.s_cpts = private unnamed_addr constant [22 x i8] c"convertPointToScreen:\00"
@.s_main = private unnamed_addr constant [11 x i8] c"mainScreen\00"
@.s_frame = private unnamed_addr constant [6 x i8] c"frame\00"
@.s_hide = private unnamed_addr constant [5 x i8] c"hide\00"
@.s_unhide = private unnamed_addr constant [7 x i8] c"unhide\00"
@.s_iskey = private unnamed_addr constant [12 x i8] c"isKeyWindow\00"
@.s_shared = private unnamed_addr constant [18 x i8] c"sharedApplication\00"
@.s_policy = private unnamed_addr constant [21 x i8] c"setActivationPolicy:\00"
@.s_alloc = private unnamed_addr constant [6 x i8] c"alloc\00"
@.s_initw = private unnamed_addr constant [45 x i8] c"initWithContentRect:styleMask:backing:defer:\00"
@.s_initf = private unnamed_addr constant [15 x i8] c"initWithFrame:\00"
@.s_title = private unnamed_addr constant [10 x i8] c"setTitle:\00"
@.s_utf8 = private unnamed_addr constant [22 x i8] c"stringWithUTF8String:\00"
@.s_setcv = private unnamed_addr constant [16 x i8] c"setContentView:\00"
@.s_center = private unnamed_addr constant [7 x i8] c"center\00"
@.s_mkey = private unnamed_addr constant [22 x i8] c"makeKeyAndOrderFront:\00"
@.s_mfirst = private unnamed_addr constant [20 x i8] c"makeFirstResponder:\00"
@.s_act = private unnamed_addr constant [27 x i8] c"activateIgnoringOtherApps:\00"
@.s_next = private unnamed_addr constant [48 x i8] c"nextEventMatchingMask:untilDate:inMode:dequeue:\00"
@.s_send = private unnamed_addr constant [11 x i8] c"sendEvent:\00"
@.s_dpast = private unnamed_addr constant [12 x i8] c"distantPast\00"
@.s_type = private unnamed_addr constant [5 x i8] c"type\00"
@.s_keycd = private unnamed_addr constant [8 x i8] c"keyCode\00"
@.s_chars = private unnamed_addr constant [28 x i8] c"charactersIgnoringModifiers\00"
@.s_length = private unnamed_addr constant [7 x i8] c"length\00"
@.s_charat = private unnamed_addr constant [18 x i8] c"characterAtIndex:\00"
@.s_locwin = private unnamed_addr constant [17 x i8] c"locationInWindow\00"
@.s_scrly = private unnamed_addr constant [16 x i8] c"scrollingDeltaY\00"
@.s_disp = private unnamed_addr constant [8 x i8] c"display\00"
@.s_visib = private unnamed_addr constant [10 x i8] c"isVisible\00"
@.s_curctx = private unnamed_addr constant [15 x i8] c"currentContext\00"
@.s_cgctx = private unnamed_addr constant [10 x i8] c"CGContext\00"
@.s_drawr = private unnamed_addr constant [10 x i8] c"drawRect:\00"
@.s_afr = private unnamed_addr constant [23 x i8] c"acceptsFirstResponder\00\00"
@.mode = private unnamed_addr constant [22 x i8] c"kCFRunLoopDefaultMode\00"
@.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
@W_fb = internal global ptr null
@W_fbw = internal global i32 320
@W_fbh = internal global i32 240
@W_scale = internal global i32 3
@W_key = internal global i32 0
@W_running = internal global i32 1
; #50 device layer — a 256-bit held-key set (8 i32) tracked from keyDown/keyUp,
; and the mouse state (position, button mask, per-frame wheel delta).
@W_held = internal global [8 x i32] zeroinitializer
@W_mx = internal global i32 0
@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
; #89 — cursor capture. @W_cursor_mode is the desired mode (0 normal, 1 hidden,
; 2 locked+relative, 3 confined). @W_cursor_hidden / @W_cursor_assoc track the
; currently-applied NSCursor hide (ref-counted, so we only toggle on a change) and
; the CGAssociateMouseAndMouseCursorPosition state, so focus loss auto-releases and
; refocus re-acquires without unbalancing the hide counter.
@W_cursor_mode = internal global i32 0
@W_cursor_hidden = internal global i32 0
@W_cursor_assoc = internal global i32 1
; -drawRect: — blit the framebuffer into the view.
; The NSRect argument is ignored, so it never appears in this signature.
define internal void @ludic_drawRect(ptr %self, ptr %sel) {
entry:
%fb = load ptr, ptr @W_fb
%null = icmp eq ptr %fb, null
br i1 %null, label %out, label %draw
draw:
%w32 = load i32, ptr @W_fbw
%h32 = load i32, ptr @W_fbh
%sc32 = load i32, ptr @W_scale
%w = sext i32 %w32 to i64
%h = sext i32 %h32 to i64
%stride = mul i64 %w, 4
%cs = call ptr @CGColorSpaceCreateDeviceRGB()
; kCGImageAlphaNoneSkipFirst (6) | kCGBitmapByteOrder32Little (8192)
%bmp = call ptr @CGBitmapContextCreate(ptr %fb, i64 %w, i64 %h, i64 8, i64 %stride, ptr %cs, i32 8198)
%img = call ptr @CGBitmapContextCreateImage(ptr %bmp)
%gcls = call ptr @objc_getClass(ptr @.c_gctx)
%sel_cur = call ptr @sel_registerName(ptr @.s_curctx)
%gctx = call ptr @objc_msgSend(ptr %gcls, ptr %sel_cur)
%sel_cg = call ptr @sel_registerName(ptr @.s_cgctx)
%ctx = call ptr @objc_msgSend(ptr %gctx, ptr %sel_cg)
call void @CGContextSetInterpolationQuality(ptr %ctx, i32 1) ; kCGInterpolationNone
%scd = sitofp i32 %sc32 to double
%wd = sitofp i32 %w32 to double
%hd = sitofp i32 %h32 to double
%dw = fmul double %wd, %scd
%dh = fmul double %hd, %scd
; The framebuffer's row 0 is the top row, and a CGImage built over it carries
; that orientation, so drawing it into the view's context lands right-side-up
; with no CTM flip. (Adding one here mirrors the game vertically — verified by
; drawing four different corners and looking at the window.)
%r0 = insertvalue %CGRect undef, double 0.0, 0
%r1 = insertvalue %CGRect %r0, double 0.0, 1
%r2 = insertvalue %CGRect %r1, double %dw, 2
%r3 = insertvalue %CGRect %r2, double %dh, 3
call void @CGContextDrawImage(ptr %ctx, %CGRect %r3, ptr %img)
call void @CGImageRelease(ptr %img)
call void @CGContextRelease(ptr %bmp)
call void @CGColorSpaceRelease(ptr %cs)
br label %out
out:
ret void
}
; -acceptsFirstResponder — so the view receives key events
define internal i8 @ludic_yes(ptr %self, ptr %sel) {
entry:
ret i8 1
}
define void @win_open(i32 %w, i32 %h, i32 %scale, ptr %title) {
entry:
store i32 %w, ptr @W_fbw
store i32 %h, ptr @W_fbh
store i32 %scale, ptr @W_scale
%appcls = call ptr @objc_getClass(ptr @.c_app)
%sel_sh = call ptr @sel_registerName(ptr @.s_shared)
%app = call ptr @objc_msgSend(ptr %appcls, ptr %sel_sh)
store ptr %app, ptr @W_app
%sel_pol = call ptr @sel_registerName(ptr @.s_policy)
%a1 = call ptr (ptr, ptr, i64) @objc_msgSend(ptr %app, ptr %sel_pol, i64 0)
; build the view class once: NSView + drawRect: + acceptsFirstResponder
%viewsuper = call ptr @objc_getClass(ptr @.c_view)
%cls = call ptr @objc_allocateClassPair(ptr %viewsuper, ptr @.c_ludic, i64 0)
%sel_draw = call ptr @sel_registerName(ptr @.s_drawr)
%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
%hd = sitofp i32 %h to double
%scd = sitofp i32 %scale to double
%pw = fmul double %wd, %scd
%ph = fmul double %hd, %scd
%r0 = insertvalue %CGRect undef, double 0.0, 0
%r1 = insertvalue %CGRect %r0, double 0.0, 1
%r2 = insertvalue %CGRect %r1, double %pw, 2
%rect = insertvalue %CGRect %r2, double %ph, 3
%wincls = call ptr @objc_getClass(ptr @.c_win)
%sel_alloc = call ptr @sel_registerName(ptr @.s_alloc)
%w0 = call ptr @objc_msgSend(ptr %wincls, ptr %sel_alloc)
%sel_initw = call ptr @sel_registerName(ptr @.s_initw)
; NSWindowStyleMaskTitled|Closable = 3, NSBackingStoreBuffered = 2
%win = call ptr (ptr, ptr, %CGRect, i64, i64, i8) @objc_msgSend(ptr %w0, ptr %sel_initw, %CGRect %rect, i64 3, i64 2, i8 0)
store ptr %win, ptr @W_win
%strcls = call ptr @objc_getClass(ptr @.c_str)
%sel_utf8 = call ptr @sel_registerName(ptr @.s_utf8)
%nstitle = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_utf8, ptr %title)
%sel_title = call ptr @sel_registerName(ptr @.s_title)
%a2 = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_title, ptr %nstitle)
%v0 = call ptr @objc_msgSend(ptr %cls, ptr %sel_alloc)
%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)
%a4 = call ptr @objc_msgSend(ptr %win, ptr %sel_center)
%sel_mkey = call ptr @sel_registerName(ptr @.s_mkey)
%a5 = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_mkey, ptr null)
%sel_mfirst = call ptr @sel_registerName(ptr @.s_mfirst)
%a6 = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_mfirst, ptr %view)
%sel_act = call ptr @sel_registerName(ptr @.s_act)
%a7 = call ptr (ptr, ptr, i8) @objc_msgSend(ptr %app, ptr %sel_act, i8 1)
ret void
}
; #50 — set (%on != 0) or clear a key's bit in the 256-bit held set @W_held.
define void @win_held_bit(i32 %k, i32 %on) {
entry:
%lo = icmp slt i32 %k, 0
%hi = icmp sgt i32 %k, 255
%oob = or i1 %lo, %hi
br i1 %oob, label %ret, label %go
go:
%w = ashr i32 %k, 5
%b = and i32 %k, 31
%m = shl i32 1, %b
%p = getelementptr [8 x i32], ptr @W_held, i32 0, i32 %w
%cur = load i32, ptr %p
%onb = icmp ne i32 %on, 0
br i1 %onb, label %set, label %clr
set:
%sv = or i32 %cur, %m
store i32 %sv, ptr %p
br label %ret
clr:
%nm = xor i32 %m, -1
%cv = and i32 %cur, %nm
store i32 %cv, ptr %p
br label %ret
ret:
ret void
}
; #50 — the ASCII value an NSEvent key event maps to (same mapping as win_poll's
; keyDown switch: arrows -> WASD, return, escape -> 27, else the first character).
; #88: escape delivers key 27 (left to the game), not 'q' — so the held-key set
; stays consistent with @W_key and a shipped game owns Esc (pause).
define i32 @ev_keyval(ptr %ev) {
entry:
%sel_kc = call ptr @sel_registerName(ptr @.s_keycd)
%kc = call i16 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_kc)
%kc32 = zext i16 %kc to i32
switch i32 %kc32, label %chars [
i32 126, label %vw
i32 125, label %vs
i32 123, label %va
i32 124, label %vd
i32 49, label %vspace
i32 36, label %vret
i32 53, label %vesc
]
vw: ret i32 119
vs: ret i32 115
va: ret i32 97
vd: ret i32 100
vspace: ret i32 32
vret: ret i32 10
vesc: ret i32 27
chars:
%sel_ch = call ptr @sel_registerName(ptr @.s_chars)
%sel_len = call ptr @sel_registerName(ptr @.s_length)
%sel_cat = call ptr @sel_registerName(ptr @.s_charat)
%s = call ptr (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_ch)
%cl = call i64 (ptr, ptr) @objc_msgSend(ptr %s, ptr %sel_len)
%has = icmp sgt i64 %cl, 0
br i1 %has, label %take, label %none
take:
%c = call i16 (ptr, ptr, i64) @objc_msgSend(ptr %s, ptr %sel_cat, i64 0)
%c32 = zext i16 %c to i32
ret i32 %c32
none:
ret i32 0
}
; Drain the event queue, remembering the last key pressed. Arrow keys map onto
; WASD; escape delivers key 27 and 'q' is an ordinary key (#88 — a windowed game
; owns Esc/quit; the headless rt_poll keeps its own 'q' = quit for scripted tests).
; #50: also track the held-key set (keyDown/keyUp) and the mouse.
define i32 @win_poll() {
entry:
store i32 0, ptr @W_key
%app = load ptr, ptr @W_app
%none = icmp eq ptr %app, null
br i1 %none, label %done, label %setup
setup:
%sel_next = call ptr @sel_registerName(ptr @.s_next)
%sel_send = call ptr @sel_registerName(ptr @.s_send)
%sel_dp = call ptr @sel_registerName(ptr @.s_dpast)
%sel_type = call ptr @sel_registerName(ptr @.s_type)
%sel_kc = call ptr @sel_registerName(ptr @.s_keycd)
%sel_ch = call ptr @sel_registerName(ptr @.s_chars)
%sel_len = call ptr @sel_registerName(ptr @.s_length)
%sel_cat = call ptr @sel_registerName(ptr @.s_charat)
%sel_utf8 = call ptr @sel_registerName(ptr @.s_utf8)
%datecls = call ptr @objc_getClass(ptr @.c_date)
%strcls = call ptr @objc_getClass(ptr @.c_str)
%mode = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_utf8, ptr @.mode)
br label %pump
pump:
%date = call ptr @objc_msgSend(ptr %datecls, ptr %sel_dp)
%ev = call ptr (ptr, ptr, i64, ptr, ptr, i8) @objc_msgSend(ptr %app, ptr %sel_next, i64 -1, ptr %date, ptr %mode, i8 1)
%got = icmp ne ptr %ev, null
br i1 %got, label %handle, label %finish
handle:
%ty = call i64 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_type)
%iskey = icmp eq i64 %ty, 10 ; NSEventTypeKeyDown
br i1 %iskey, label %key, label %notkey
notkey:
%isup = icmp eq i64 %ty, 11 ; NSEventTypeKeyUp
br i1 %isup, label %keyup, label %mouse
keyup: ; #50 — release the held key
%uv = call i32 @ev_keyval(ptr %ev)
call void @win_held_bit(i32 %uv, i32 0)
br label %forward
mouse: ; #50 — mouse buttons + wheel
%ml_d = icmp eq i64 %ty, 1 ; NSEventTypeLeftMouseDown
br i1 %ml_d, label %lset, label %ml_u
lset:
%lb = load i32, ptr @W_mbtn
%lb2 = or i32 %lb, 1
store i32 %lb2, ptr @W_mbtn
br label %forward
ml_u:
%ml_up = icmp eq i64 %ty, 2 ; NSEventTypeLeftMouseUp
br i1 %ml_up, label %lclr, label %mr_d
lclr:
%lc = load i32, ptr @W_mbtn
%lc2 = and i32 %lc, -2
store i32 %lc2, ptr @W_mbtn
br label %forward
mr_d:
%mrd = icmp eq i64 %ty, 3 ; NSEventTypeRightMouseDown
br i1 %mrd, label %rset, label %mr_u
rset:
%rb = load i32, ptr @W_mbtn
%rb2 = or i32 %rb, 2
store i32 %rb2, ptr @W_mbtn
br label %forward
mr_u:
%mru = icmp eq i64 %ty, 4 ; NSEventTypeRightMouseUp
br i1 %mru, label %rclr, label %scroll
rclr:
%rc = load i32, ptr @W_mbtn
%rc2 = and i32 %rc, -3
store i32 %rc2, ptr @W_mbtn
br label %forward
scroll:
%isw = icmp eq i64 %ty, 22 ; NSEventTypeScrollWheel
br i1 %isw, label %wdo, label %forward
wdo:
%sel_sy = call ptr @sel_registerName(ptr @.s_scrly)
%dy = call double (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_sy)
%dyi = fptosi double %dy to i32
%wv = load i32, ptr @W_wheel
%wv2 = add i32 %wv, %dyi
store i32 %wv2, ptr @W_wheel
br label %forward
key:
%kdv = call i32 @ev_keyval(ptr %ev) ; #50 — press the held key
call void @win_held_bit(i32 %kdv, i32 1)
%kc = call i16 (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_kc)
%kc32 = zext i16 %kc to i32
switch i32 %kc32, label %fromchars [
i32 126, label %k_w
i32 125, label %k_s
i32 123, label %k_a
i32 124, label %k_d
i32 49, label %k_space
i32 36, label %k_ret
i32 53, label %k_esc
]
k_w: store i32 119, ptr @W_key br label %forward
k_s: store i32 115, ptr @W_key br label %forward
k_a: store i32 97, ptr @W_key br label %forward
k_d: store i32 100, ptr @W_key br label %forward
k_space: store i32 32, ptr @W_key br label %forward
k_ret: store i32 10, ptr @W_key br label %forward
k_esc:
; Escape is delivered as key 27 and left to the game (pause menus etc.).
; It used to force-quit the window, which a shipped game cannot tolerate.
store i32 27, ptr @W_key
br label %forward
fromchars:
%chars = call ptr (ptr, ptr) @objc_msgSend(ptr %ev, ptr %sel_ch)
%clen = call i64 (ptr, ptr) @objc_msgSend(ptr %chars, ptr %sel_len)
%hasch = icmp sgt i64 %clen, 0
br i1 %hasch, label %takechar, label %forward
takechar:
%ch = call i16 (ptr, ptr, i64) @objc_msgSend(ptr %chars, ptr %sel_cat, i64 0)
%ch32 = zext i16 %ch to i32
store i32 %ch32, ptr @W_key
; 'q' is an ordinary key in a windowed game (the headless test driver keeps its
; own 'q' = quit in rt_poll); a shipped game must not die on a typo.
br label %forward
forward:
; key events are consumed by win_poll itself; forwarding them to -sendEvent:
; hits an unhandled responder chain and AppKit plays the system "funk" beep.
; Mouse / other events are still forwarded so the window behaves normally.
%fwd_isdown = icmp eq i64 %ty, 10
%fwd_isup = icmp eq i64 %ty, 11
%fwd_iskey = or i1 %fwd_isdown, %fwd_isup
br i1 %fwd_iskey, label %pump, label %dosend
dosend:
%fwd = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %app, ptr %sel_send, ptr %ev)
br label %pump
finish:
call void @win_cursor_maintain() ; #89 — keep the cursor state (auto-release on focus loss)
; a closed window ends the run
%win = load ptr, ptr @W_win
%hw = icmp ne ptr %win, null
br i1 %hw, label %checkvis, label %done
checkvis:
%sel_vis = call ptr @sel_registerName(ptr @.s_visib)
%vis = call i8 (ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_vis)
%gone = icmp eq i8 %vis, 0
br i1 %gone, label %closed, label %done
closed:
store i32 0, ptr @W_running
br label %done
done:
%k = load i32, ptr @W_key
ret i32 %k
}
define void @win_present(ptr %fb, i32 %w, i32 %h) {
entry:
store ptr %fb, ptr @W_fb
store i32 %w, ptr @W_fbw
store i32 %h, ptr @W_fbh
%view = load ptr, ptr @W_view
%none = icmp eq ptr %view, null
br i1 %none, label %out, label %show
show:
%sel_disp = call ptr @sel_registerName(ptr @.s_disp)
%d = call ptr @objc_msgSend(ptr %view, ptr %sel_disp)
%u = call i32 @usleep(i32 16000) ; ~60 fps
br label %out
out:
ret void
}
define i32 @win_running() {
entry:
%r = load i32, ptr @W_running
ret i32 %r
}
define void @win_close() {
entry:
; #89 — restore the cursor so it is never left hidden / dissociated after quit.
store i32 0, ptr @W_cursor_mode
call void @cursor_apply(i32 0, i32 1)
store i32 0, ptr @W_running
ret void
}
; #50 — copy the 8-word held-key set into the caller's buffer.
define void @win_held(ptr %out) {
entry:
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %ni, %body ]
%done = icmp sge i32 %i, 8
br i1 %done, label %ret, label %body
body:
%sp = getelementptr [8 x i32], ptr @W_held, 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
}
; #50 — write [x, y, button-mask, wheel-delta] into the caller's buffer, then
; reset the accumulated wheel delta (it is per-frame). Position is tracked in
; 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.
; #89 — only mode 2 (true lock / FPS mouselook) dissociates the OS cursor, so its
; mouseLocation is frozen; accumulate CGGetLastMouseDelta into a virtual cursor
; (clamped to the framebuffer) so Input.mouse_dx/dy report the motion and
; Input.mouse_x/y give a reticle position. Mode 3 (confined) keeps the cursor
; ASSOCIATED, so it stays on the absolute path below — absolute mouse aim
; (aim_mode 0) works in a confined-cursor game, which is the whole point.
%cmode = load i32, ptr @W_cursor_mode
%isrel = icmp eq i32 %cmode, 2
br i1 %isrel, label %reldelta, label %abspos
reldelta:
%dxp = alloca i32
%dyp = alloca i32
call void @CGGetLastMouseDelta(ptr %dxp, ptr %dyp)
%ddx = load i32, ptr %dxp
%ddy = load i32, ptr %dyp
%omx = load i32, ptr @W_mx
%omy = load i32, ptr @W_my
%nmx = add i32 %omx, %ddx
%nmy = add i32 %omy, %ddy
; clamp x to [0, fbw-1]
%fbwv = load i32, ptr @W_fbw
%fbwm = sub i32 %fbwv, 1
%xlo = icmp slt i32 %nmx, 0
%nmx1 = select i1 %xlo, i32 0, i32 %nmx
%xhi = icmp sgt i32 %nmx1, %fbwm
%nmx2 = select i1 %xhi, i32 %fbwm, i32 %nmx1
; clamp y to [0, fbh-1]
%fbhv = load i32, ptr @W_fbh
%fbhm = sub i32 %fbhv, 1
%ylo = icmp slt i32 %nmy, 0
%nmy1 = select i1 %ylo, i32 0, i32 %nmy
%yhi = icmp sgt i32 %nmy1, %fbhm
%nmy2 = select i1 %yhi, i32 %fbhm, i32 %nmy1
store i32 %nmx2, ptr @W_mx
store i32 %nmy2, ptr @W_my
br label %emit
abspos:
%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
%ix0 = fptosi double %fx to i32
%iy0 = fptosi double %fy to i32
; clamp the reported position to [0, fbw-1] x [0, fbh-1] so the reticle stays in
; the play area even when the (hidden) OS cursor strays past the window edge.
%fbwa = load i32, ptr @W_fbw
%fbwam = sub i32 %fbwa, 1
%fbham = sub i32 %fbh, 1
%axlo = icmp slt i32 %ix0, 0
%ix1 = select i1 %axlo, i32 0, i32 %ix0
%axhi = icmp sgt i32 %ix1, %fbwam
%ix = select i1 %axhi, i32 %fbwam, i32 %ix1
%aylo = icmp slt i32 %iy0, 0
%iy1 = select i1 %aylo, i32 0, i32 %iy0
%ayhi = icmp sgt i32 %iy1, %fbham
%iy = select i1 %ayhi, i32 %fbham, i32 %iy1
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
%my = load i32, ptr @W_my
%p1 = getelementptr i32, ptr %out, i32 1
store i32 %my, ptr %p1
%mb = load i32, ptr @W_mbtn
%p2 = getelementptr i32, ptr %out, i32 2
store i32 %mb, ptr %p2
%wh = load i32, ptr @W_wheel
%p3 = getelementptr i32, ptr %out, i32 3
store i32 %wh, ptr %p3
store i32 0, ptr @W_wheel
ret void
}
; ============================================================================
; #89 — cursor capture. Apply the currently-wanted hidden / associated state,
; toggling only on a change so the ref-counted [NSCursor hide]/[unhide] stays
; balanced across frames and focus changes.
define void @cursor_apply(i32 %wh, i32 %wa) {
entry:
%ch = load i32, ptr @W_cursor_hidden
%hdiff = icmp ne i32 %ch, %wh
br i1 %hdiff, label %doh, label %skiph
doh:
%curscls = call ptr @objc_getClass(ptr @.c_cursor)
%wanth = icmp ne i32 %wh, 0
br i1 %wanth, label %hide, label %unhide
hide:
%selhide = call ptr @sel_registerName(ptr @.s_hide)
%r1 = call ptr (ptr, ptr) @objc_msgSend(ptr %curscls, ptr %selhide)
br label %storeh
unhide:
%selunh = call ptr @sel_registerName(ptr @.s_unhide)
%r2 = call ptr (ptr, ptr) @objc_msgSend(ptr %curscls, ptr %selunh)
br label %storeh
storeh:
store i32 %wh, ptr @W_cursor_hidden
br label %skiph
skiph:
%ca = load i32, ptr @W_cursor_assoc
%adiff = icmp ne i32 %ca, %wa
br i1 %adiff, label %doa, label %skipa
doa:
%rc = call i32 @CGAssociateMouseAndMouseCursorPosition(i32 %wa)
store i32 %wa, ptr @W_cursor_assoc
br label %skipa
skipa:
ret void
}
; Re-derive the wanted state from the mode + window key status, then apply it.
; Called on a mode change and once per frame from win_poll, so losing focus
; (Cmd-Tab) auto-releases the cursor and refocusing re-acquires it.
define void @win_cursor_maintain() {
entry:
%mode = load i32, ptr @W_cursor_mode
%win = load ptr, ptr @W_win
%nowin = icmp eq ptr %win, null
br i1 %nowin, label %release, label %askkey
askkey:
%selk = call ptr @sel_registerName(ptr @.s_iskey)
%k8 = call i8 (ptr, ptr) @objc_msgSend(ptr %win, ptr %selk)
%iskey = icmp ne i8 %k8, 0
br i1 %iskey, label %keyed, label %release
release:
; not focused (or no window): show the cursor and reconnect it
call void @cursor_apply(i32 0, i32 1)
ret void
keyed:
; want_hidden for every capture mode: 1 (hidden), 2 (locked) and 3 (confined).
; A confined-cursor game hides the OS cursor and draws its own reticle.
%h1 = icmp eq i32 %mode, 1
%h2 = icmp eq i32 %mode, 2
%h3 = icmp eq i32 %mode, 3
%hor = or i1 %h1, %h2
%wh = or i1 %hor, %h3
%whi = zext i1 %wh to i32
; dissociate (relative deltas) ONLY for mode 2 (true lock / FPS mouselook).
; Mode 3 (confined) stays associated so its absolute position stays valid.
%a2 = icmp eq i32 %mode, 2
%wa = xor i1 %a2, true
%wai = zext i1 %wa to i32
call void @cursor_apply(i32 %whi, i32 %wai)
; mode 3 (confined): physically hold the cursor inside the window each frame.
%ism3 = icmp eq i32 %mode, 3
br i1 %ism3, label %confine, label %noconf
confine:
call void @win_cursor_confine()
br label %noconf
noconf:
ret void
}
; #89 — confine the (hidden) cursor to the content area in mode 3. Because mode 3
; keeps the cursor ASSOCIATED (so absolute mouse aim works), the OS cursor is still
; free to leave the window; without this a click outside would land outside and drop
; focus. Each frame we read the window-local cursor point, and if it has strayed past
; the content rect we warp it back to the nearest inside point. Only warps when the
; cursor is actually out, so an in-window cursor is untouched.
define void @win_cursor_confine() {
entry:
%win = load ptr, ptr @W_win
%nowin = icmp eq ptr %win, null
br i1 %nowin, label %done, label %go
go:
%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
; content bounds in points: [0, fbw*scale] x [0, fbh*scale]
%sc = load i32, ptr @W_scale
%scd = sitofp i32 %sc to double
%fbw = load i32, ptr @W_fbw
%fbh = load i32, ptr @W_fbh
%fbwd = sitofp i32 %fbw to double
%fbhd = sitofp i32 %fbh to double
%cw = fmul double %fbwd, %scd
%ch = fmul double %fbhd, %scd
%cwm = fsub double %cw, 1.000000e+00
%chm = fsub double %ch, 1.000000e+00
; clamp px to [1, cw-1]
%pxlo = fcmp olt double %px, 1.000000e+00
%px1 = select i1 %pxlo, double 1.000000e+00, double %px
%pxhi = fcmp ogt double %px1, %cwm
%cpx = select i1 %pxhi, double %cwm, double %px1
; clamp py to [1, ch-1]
%pylo = fcmp olt double %py, 1.000000e+00
%py1 = select i1 %pylo, double 1.000000e+00, double %py
%pyhi = fcmp ogt double %py1, %chm
%cpy = select i1 %pyhi, double %chm, double %py1
; warp only if the cursor actually strayed
%dx = fcmp one double %cpx, %px
%dy = fcmp one double %cpy, %py
%need = or i1 %dx, %dy
br i1 %need, label %warp, label %done
warp:
; clamped window-local point -> global screen coords (bottom-left origin, y up)
%cp0 = insertvalue %NSPoint undef, double %cpx, 0
%cp = insertvalue %NSPoint %cp0, double %cpy, 1
%sel_cps = call ptr @sel_registerName(ptr @.s_cpts)
%gp = call %NSPoint (ptr, ptr, %NSPoint) @objc_msgSend(ptr %win, ptr %sel_cps, %NSPoint %cp)
%gx = extractvalue %NSPoint %gp, 0
%gy = extractvalue %NSPoint %gp, 1
; main screen height for the flip to CG global coords (top-left origin, y down)
%scrcls = call ptr @objc_getClass(ptr @.c_screen)
%sel_main = call ptr @sel_registerName(ptr @.s_main)
%scr = call ptr (ptr, ptr) @objc_msgSend(ptr %scrcls, ptr %sel_main)
%sel_fr = call ptr @sel_registerName(ptr @.s_frame)
%fr = call %CGRect (ptr, ptr) @objc_msgSend(ptr %scr, ptr %sel_fr)
%sh = extractvalue %CGRect %fr, 3
%warpy = fsub double %sh, %gy
%wp0 = insertvalue %NSPoint undef, double %gx, 0
%wp = insertvalue %NSPoint %wp0, double %warpy, 1
%rc = call i32 @CGWarpMouseCursorPosition(%NSPoint %wp)
br label %done
done:
ret void
}
; the Ludic entry point: set the desired cursor mode and apply it now.
define void @win_cursor_mode(i32 %mode) {
entry:
store i32 %mode, ptr @W_cursor_mode
call void @win_cursor_maintain()
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
}