ffi 3.2 C3: migrate uikit.sx RunLoop + display-timing cluster

Third cluster: NSRunLoop and CADisplayLink move to declarative
`#objc_class` blocks.

Classes declared:

- NSRunLoop     → currentRunLoop (class)
- CADisplayLink → displayLinkWithTarget_selector (class),
                  addToRunLoop_forMode (instance),
                  targetTimestamp (instance), duration (instance)

The display-link instance is created with the new typed call shape:

    link := CADisplayLink.displayLinkWithTarget_selector(plat.gl_view, sel_tick);
    plat.display_link = xx link;  // keep the *void slot in the
                                   // platform struct for ABI parity
    runloop := NSRunLoop.currentRunLoop();
    link.addToRunLoop_forMode(runloop, mode_ns);

The `sxTick:` callback's `link: *void` param is cast to
`*CADisplayLink` at function entry so the body's `link.duration()` /
`link.targetTimestamp()` calls type-check.

Two now-redundant `objc_getClass(...)` lookups for CADisplayLink /
NSRunLoop are gone — the class slots come from the declarative
declarations via `__sx_objc_class_init`.

166/166 tests; chess builds clean on macOS / iOS / Android.
This commit is contained in:
agra
2026-05-25 17:15:27 +03:00
parent 17775b27a4
commit 2a7c8e0a6f
2 changed files with 36 additions and 12 deletions

View File

@@ -100,6 +100,19 @@ NSNotificationCenter :: #foreign #objc_class("NSNotificationCenter") {
addObserver_selector_name_object :: (self: *Self, observer: *void, sel: *void, name: *void, obj: *void);
}
// ── RunLoop + display timing (Phase 3.2 C3) ────────────────────────────
NSRunLoop :: #foreign #objc_class("NSRunLoop") {
currentRunLoop :: () -> *NSRunLoop;
}
CADisplayLink :: #foreign #objc_class("CADisplayLink") {
displayLinkWithTarget_selector :: (target: *void, sel: *void) -> *CADisplayLink;
addToRunLoop_forMode :: (self: *Self, runloop: *NSRunLoop, mode: *void);
targetTimestamp :: (self: *Self) -> f64;
duration :: (self: *Self) -> f64;
}
// GLenum constants for renderbuffer/framebuffer setup that aren't in opengl.sx's
// loader path (they live on the framework's symbol table directly).
GL_RENDERBUFFER :u32: 0x8D41;
@@ -534,8 +547,9 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
UIViewController := objc_getClass("UIViewController".ptr);
SxGLView := objc_getClass("SxGLView".ptr);
SxMetalView := objc_getClass("SxMetalView".ptr);
CADisplayLink := objc_getClass("CADisplayLink".ptr);
NSRunLoop := objc_getClass("NSRunLoop".ptr);
// CADisplayLink and NSRunLoop class objects come from the
// declarative `#objc_class` slots populated by emit_llvm's
// __sx_objc_class_init constructor — no local objc_getClass needed.
win_raw := #objc_call(*void)(UIWindow, "alloc");
plat.window = #objc_call(*void)(win_raw, "initWithWindowScene:", scene);
@@ -619,10 +633,11 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
// a SEL value (not a dispatch selector), so it still goes through
// sel_registerName.
sel_tick := sel_registerName("sxTick:".ptr);
plat.display_link = #objc_call(*void)(CADisplayLink, "displayLinkWithTarget:selector:", plat.gl_view, sel_tick);
runloop := #objc_call(*void)(NSRunLoop, "currentRunLoop");
mode := ns_string("kCFRunLoopDefaultMode".ptr);
#objc_call(void)(plat.display_link, "addToRunLoop:forMode:", runloop, mode);
link := CADisplayLink.displayLinkWithTarget_selector(plat.gl_view, sel_tick);
plat.display_link = xx link;
runloop := NSRunLoop.currentRunLoop();
mode_ns := ns_string("kCFRunLoopDefaultMode".ptr);
link.addToRunLoop_forMode(runloop, mode_ns);
NSLog(ns_string("[sx] UIKitPlatform booted\n".ptr));
}
@@ -682,7 +697,8 @@ uikit_gl_view_layer_class :: (cls: *void, _cmd: *void) -> *void callconv(.c) {
objc_getClass("CAEAGLLayer".ptr);
}
uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) {
uikit_gl_view_tick :: (self: *void, _cmd: *void, link_raw: *void) callconv(.c) {
link : *CADisplayLink = xx link_raw;
if g_uikit_plat == null { return; }
plat := g_uikit_plat;
@@ -696,7 +712,7 @@ uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) {
// and keyboardLayoutGuide — none eliminated the lag without
// cascade-breaking the GL view's frame.
if plat.kb_animating {
target_ts := #objc_call(f64)(link, "targetTimestamp");
target_ts := link.targetTimestamp();
elapsed := target_ts - plat.kb_anim_start;
// Negative elapsed can happen if the just-fired willChangeFrame
// set kb_anim_start to a wall time AFTER the tick already
@@ -723,11 +739,11 @@ uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) {
if !plat.has_frame_closure { return; }
if !plat.gl_initialized { return; }
dur_d := #objc_call(f64)(link, "duration");
dur_d := link.duration();
plat.delta_time = xx dur_d;
// Stash the targetTimestamp so begin_frame can hand it down to the
// game in FrameContext for Metal presentDrawable:atTime:.
plat.last_target_ts = #objc_call(f64)(link, "targetTimestamp");
plat.last_target_ts = link.targetTimestamp();
fn := plat.frame_closure;
fn();