ffi 1.31: uikit_scene_will_connect_ios via #objc_call

The biggest Phase 1D cluster: the iOS scene-lifecycle entry that runs
at every launch. UIWindow alloc/init, UIViewController alloc/init, GL
view alloc/init/install, root-view-controller wiring, layer access +
setOpaque:, EAGL drawable-properties dictionary build,
screen/nativeScale DPI scaling, makeKeyAndVisible, UITextField subview
install, CADisplayLink construct + addToRunLoop. Every return shape
this file uses (void, *void, f64) and every arg shape (BOOL via `xx
0`/`xx 1`, multi-arg selectors `displayLinkWithTarget:selector:` and
`setObject:forKey:`) is exercised by this single launch.

Net -44 lines on this commit (104 → 60). Also drops a stale
`EAGLContext := objc_getClass(...)` decl that wasn't referenced inside
this function — EAGL context creation lives in uikit_create_gl_context
(already migrated in 1.29). uikit.sx is now 868 lines (-69 cumulative
across Phase 1D).

iOS-sim chess regression smoke: app launches cleanly, board renders
with status-bar clearance, sharp DPI scaling, compositor working,
display-link tick driving frames. Every part of the migrated function
is on the launch path and all of it succeeds.
This commit is contained in:
agra
2026-05-19 20:57:09 +03:00
parent e52f9f275e
commit b3558c3274

View File

@@ -496,77 +496,37 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
UIViewController := objc_getClass("UIViewController".ptr);
SxGLView := objc_getClass("SxGLView".ptr);
SxMetalView := objc_getClass("SxMetalView".ptr);
EAGLContext := objc_getClass("EAGLContext".ptr);
CADisplayLink := objc_getClass("CADisplayLink".ptr);
NSRunLoop := objc_getClass("NSRunLoop".ptr);
sel_alloc := sel_registerName("alloc".ptr);
sel_init := sel_registerName("init".ptr);
sel_init_with_scene := sel_registerName("initWithWindowScene:".ptr);
sel_init_with_frame := sel_registerName("initWithFrame:".ptr);
sel_view := sel_registerName("view".ptr);
sel_set_root_vc := sel_registerName("setRootViewController:".ptr);
sel_make_key_visible := sel_registerName("makeKeyAndVisible".ptr);
sel_add_subview := sel_registerName("addSubview:".ptr);
sel_set_frame := sel_registerName("setFrame:".ptr);
sel_bounds := sel_registerName("bounds".ptr);
sel_set_autoresizing := sel_registerName("setAutoresizingMask:".ptr);
sel_init_with_api := sel_registerName("initWithAPI:".ptr);
sel_set_current_ctx := sel_registerName("setCurrentContext:".ptr);
sel_layer := sel_registerName("layer".ptr);
sel_set_content_scale := sel_registerName("setContentScaleFactor:".ptr);
sel_screen := sel_registerName("screen".ptr);
sel_native_scale := sel_registerName("nativeScale".ptr);
sel_link_with_target := sel_registerName("displayLinkWithTarget:selector:".ptr);
sel_add_to_runloop := sel_registerName("addToRunLoop:forMode:".ptr);
sel_current_runloop := sel_registerName("currentRunLoop".ptr);
sel_tick := sel_registerName("sxTick:".ptr);
msg_o : (*void, *void) -> *void = xx objc_msgSend;
msg_v : (*void, *void) -> void = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void = xx objc_msgSend;
msg_ooo : (*void, *void, *void) -> *void = xx objc_msgSend;
msg_oso : (*void, *void, *void, *void) -> *void = xx objc_msgSend;
msg_oi32 : (*void, *void, s32) -> *void = xx objc_msgSend;
msg_oou64 : (*void, *void, u64) -> void = xx objc_msgSend;
// CGFloat-returning msgSend. CGFloat is `double` on 64-bit Apple — reading
// it as f32 reads the low 32 bits of `d0` which isn't a valid float
// representation of the underlying double, so the value comes back as 0.
msg_d : (*void, *void) -> f64 = xx objc_msgSend;
msg_odbl : (*void, *void, f64) -> void = xx objc_msgSend;
win_raw := msg_o(UIWindow, sel_alloc);
plat.window = msg_ooo(win_raw, sel_init_with_scene, scene);
win_raw := #objc_call(*void)(UIWindow, "alloc");
plat.window = #objc_call(*void)(win_raw, "initWithWindowScene:", scene);
// Make the scene delegate own the window so iOS retains it. Per the
// scene-based lifecycle, the scene delegate is expected to provide the
// UIWindow via -window/-setWindow:.
sel_set_window := sel_registerName("setWindow:".ptr);
msg_oo(delegate, sel_set_window, plat.window);
#objc_call(void)(delegate, "setWindow:", plat.window);
vc_raw := msg_o(UIViewController, sel_alloc);
plat.root_vc = msg_o(vc_raw, sel_init);
vc_raw := #objc_call(*void)(UIViewController, "alloc");
plat.root_vc = #objc_call(*void)(vc_raw, "init");
// Allocate either SxGLView or SxMetalView based on gpu_mode and install
// it as the VC's view. The view's +layerClass override gives us the
// right CAEAGLLayer / CAMetalLayer subclass. Setting it BEFORE
// setRootViewController avoids the VC lazy-loading a default view.
view_class := if plat.gpu_mode == .gles then SxGLView else SxMetalView;
glv_raw := msg_o(view_class, sel_alloc);
plat.gl_view = msg_o(glv_raw, sel_init);
sel_set_view := sel_registerName("setView:".ptr);
msg_oo(plat.root_vc, sel_set_view, plat.gl_view);
glv_raw := #objc_call(*void)(view_class, "alloc");
plat.gl_view = #objc_call(*void)(glv_raw, "init");
#objc_call(void)(plat.root_vc, "setView:", plat.gl_view);
msg_oo(plat.window, sel_set_root_vc, plat.root_vc);
#objc_call(void)(plat.window, "setRootViewController:", plat.root_vc);
plat.gl_layer = msg_o(plat.gl_view, sel_layer);
plat.gl_layer = #objc_call(*void)(plat.gl_view, "layer");
// Mark the layer opaque (no compositor blend). Required for EAGL +
// recommended for Metal (CAMetalLayer.opaque defaults to YES but doesn't
// hurt to be explicit).
sel_set_opaque := sel_registerName("setOpaque:".ptr);
msg_obool : (*void, *void, u8) -> void = xx objc_msgSend;
msg_obool(plat.gl_layer, sel_set_opaque, 1);
#objc_call(void)(plat.gl_layer, "setOpaque:", xx 1);
if plat.gpu_mode == .gles {
// EAGL drawable properties dict required by
@@ -575,13 +535,8 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
// allocation silently fails and the framebuffer reports INCOMPLETE.
NSMutableDictionary := objc_getClass("NSMutableDictionary".ptr);
NSNumber := objc_getClass("NSNumber".ptr);
sel_dictionary := sel_registerName("dictionary".ptr);
sel_set_obj_for_key := sel_registerName("setObject:forKey:".ptr);
sel_number_bool := sel_registerName("numberWithBool:".ptr);
sel_set_drawable := sel_registerName("setDrawableProperties:".ptr);
msg_oio : (*void, *void, u8) -> *void = xx objc_msgSend;
ns_no := msg_oio(NSNumber, sel_number_bool, 0);
ns_no := #objc_call(*void)(NSNumber, "numberWithBool:", xx 0);
// The EAGL dict keys/values must be the framework-provided NSString
// constants (pointer identity is checked) — dlsym them from OpenGLES.
@@ -589,11 +544,10 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
colorformat_key := uikit_extern_nsstring("kEAGLDrawablePropertyColorFormat".ptr);
rgba8_value := uikit_extern_nsstring("kEAGLColorFormatRGBA8".ptr);
dict := msg_o(NSMutableDictionary, sel_dictionary);
msg_o3 : (*void, *void, *void, *void) -> void = xx objc_msgSend;
msg_o3(dict, sel_set_obj_for_key, ns_no, retained_key);
msg_o3(dict, sel_set_obj_for_key, rgba8_value, colorformat_key);
msg_oo(plat.gl_layer, sel_set_drawable, dict);
dict := #objc_call(*void)(NSMutableDictionary, "dictionary");
#objc_call(void)(dict, "setObject:forKey:", ns_no, retained_key);
#objc_call(void)(dict, "setObject:forKey:", rgba8_value, colorformat_key);
#objc_call(void)(plat.gl_layer, "setDrawableProperties:", dict);
}
// EAGLContext + load_gl were already done in uikit_create_gl_context()
@@ -603,35 +557,37 @@ uikit_scene_will_connect_ios :: (delegate: *void, scene: *void) {
// Match the layer's drawable scale to the screen's native scale so we get
// pixel-accurate rendering on retina displays. CGFloat is `double` on
// 64-bit Apple platforms; reading as f32 would clobber the value.
screen := msg_o(plat.window, sel_screen);
scale := msg_d(screen, sel_native_scale);
screen := #objc_call(*void)(plat.window, "screen");
scale := #objc_call(f64)(screen, "nativeScale");
plat.dpi_scale = xx scale;
msg_odbl(plat.gl_view, sel_set_content_scale, scale);
#objc_call(void)(plat.gl_view, "setContentScaleFactor:", scale);
// Renderbuffer is allocated lazily in -[SxGLView layoutSubviews] once
// the layer has its real on-screen bounds. makeKeyAndVisible triggers
// a layout pass; layoutSubviews calls uikit_setup_renderbuffer.
msg_v(plat.window, sel_make_key_visible);
#objc_call(void)(plat.window, "makeKeyAndVisible");
// Hidden UITextField as the firstResponder source for show_keyboard /
// hide_keyboard. Lives as a subview of the GL view so it's in the
// responder chain but is sized 0×0 so it can't be tapped.
UITextField := objc_getClass("UITextField".ptr);
sel_add_subview := sel_registerName("addSubview:".ptr);
tf_raw := msg_o(UITextField, sel_alloc);
plat.text_field = msg_o(tf_raw, sel_init);
msg_oo(plat.gl_view, sel_add_subview, plat.text_field);
tf_raw := #objc_call(*void)(UITextField, "alloc");
plat.text_field = #objc_call(*void)(tf_raw, "init");
#objc_call(void)(plat.gl_view, "addSubview:", plat.text_field);
// (Keyboard observer is registered in didFinishLaunching via
// uikit_subscribe_keyboard_notifications — it's app-level, not scene-
// level, so it doesn't belong here.)
// CADisplayLink: vsync-driven tick into our SxGLView.
plat.display_link = msg_oso(CADisplayLink, sel_link_with_target, plat.gl_view, sel_tick);
runloop := msg_o(NSRunLoop, sel_current_runloop);
// CADisplayLink: vsync-driven tick into our SxGLView. The second arg is
// 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);
msg_oso(plat.display_link, sel_add_to_runloop, runloop, mode);
#objc_call(void)(plat.display_link, "addToRunLoop:forMode:", runloop, mode);
NSLog(ns_string("[sx] UIKitPlatform booted\n".ptr));
}