ffi M3.2: SxSceneDelegate migrated + #implements protocol conformance
Migrates SxSceneDelegate from the hand-rolled
objc_allocateClassPair + class_addMethod + class_addProtocol
sequence to the declarative form:
SxSceneDelegate :: #objc_class("SxSceneDelegate") {
#extends UIResponder;
#implements UISceneDelegate;
#implements UIWindowSceneDelegate;
scene_willConnectToSession_options :: (self, scene, session, options) { ... }
window :: (self) -> *void { ... }
setWindow :: (self, w) { ... }
}
emit_llvm now honors '#implements' in the class-pair init
constructor — for each #implements ProtocolAlias on the cache
entry's AST, emit before objc_registerClassPair:
proto = objc_getProtocol("ProtocolName")
class_addProtocol(cls, proto)
iOS checks 'class_conformsToProtocol' when instantiating scene
delegates; without the conformance the runtime silently rejects
the class and a default scene with no delegate gets created
instead. The protocol-getter returns null on dead-strip /
runtime mismatch (rare but possible) — the runtime treats
class_addProtocol(cls, null) as a no-op, so no explicit null
check needed.
Method bodies forward to the existing legacy free IMP functions
(uikit_scene_will_connect, uikit_window_getter,
uikit_window_setter) so we don't have to inline the scene-
connect setup logic (~80 lines).
uikit_register_classes is now tiny — just the two remaining
view-class helpers (M3.3 SxGLView + M3.4 SxMetalView). M3.5
deletes the function entirely once those land.
Chess on iOS-sim: board renders, scene delegate fires, touch
events route correctly. 183 example tests + zig build test
green.
This commit is contained in:
@@ -197,6 +197,30 @@ SxAppDelegate :: #objc_class("SxAppDelegate") {
|
||||
init :: (self: *SxAppDelegate) -> *SxAppDelegate;
|
||||
}
|
||||
|
||||
// SxSceneDelegate — iOS 13+ scene-based lifecycle delegate.
|
||||
// UIApplicationSceneManifest names this in Info.plist; iOS
|
||||
// instantiates it via scene-session connection. Replaces the M3.2
|
||||
// hand-rolled registration. Two `#implements` declarations
|
||||
// formally conform to the scene-delegate protocols — iOS rejects
|
||||
// the class otherwise.
|
||||
SxSceneDelegate :: #objc_class("SxSceneDelegate") {
|
||||
#extends UIResponder;
|
||||
#implements UISceneDelegate;
|
||||
#implements UIWindowSceneDelegate;
|
||||
|
||||
scene_willConnectToSession_options :: (self: *Self, scene: *void, session: *void, options: *void) {
|
||||
uikit_scene_will_connect(xx self, xx 0, scene, session, options);
|
||||
}
|
||||
|
||||
window :: (self: *Self) -> *void {
|
||||
return uikit_window_getter(xx self, xx 0);
|
||||
}
|
||||
|
||||
setWindow :: (self: *Self, w: *void) {
|
||||
uikit_window_setter(xx self, xx 0, w);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -428,49 +452,12 @@ uikit_chdir_to_bundle :: () {
|
||||
|
||||
uikit_register_classes :: () {
|
||||
inline if OS == .ios {
|
||||
// SxAppDelegate is now declared as `#objc_class("SxAppDelegate")`
|
||||
// (M3.1) — the compiler synthesises its IMPs and class-pair
|
||||
// registration at module init. The old hand-rolled
|
||||
// objc_allocateClassPair + class_addMethod sequence is gone.
|
||||
|
||||
UIResponder_cls := objc_getClass("UIResponder".ptr);
|
||||
|
||||
// SxSceneDelegate handles the per-scene UI setup. iOS 13+ scene-based
|
||||
// lifecycle: didFinishLaunching is too early for the window — the
|
||||
// UIWindowScene doesn't connect until `scene:willConnectTo:options:`.
|
||||
// The class is named in Info.plist's UIApplicationSceneManifest →
|
||||
// UISceneDelegateClassName.
|
||||
SxSceneDelegate := objc_allocateClassPair(UIResponder_cls, "SxSceneDelegate".ptr, 0);
|
||||
|
||||
class_addMethod(SxSceneDelegate,
|
||||
sel_registerName("scene:willConnectToSession:options:".ptr),
|
||||
xx uikit_scene_will_connect, "v@:@@@".ptr);
|
||||
class_addMethod(SxSceneDelegate,
|
||||
sel_registerName("window".ptr),
|
||||
xx uikit_window_getter, "@@:".ptr);
|
||||
class_addMethod(SxSceneDelegate,
|
||||
sel_registerName("setWindow:".ptr),
|
||||
xx uikit_window_setter, "v@:@".ptr);
|
||||
|
||||
// Formal protocol conformance is required for UISceneDelegate
|
||||
// (iOS checks -[cls conformsToProtocol:@protocol(UISceneDelegate)]
|
||||
// before instantiating; without it the class is silently rejected
|
||||
// with "does not conform to the UISceneDelegate protocol" in the
|
||||
// log and a default scene with no delegate is created instead).
|
||||
// Add the protocol BEFORE registerClassPair — the runtime locks
|
||||
// the class layout after registration.
|
||||
UISceneDelegateProto := objc_getProtocol("UISceneDelegate".ptr);
|
||||
UIWindowSceneDelegateProto := objc_getProtocol("UIWindowSceneDelegate".ptr);
|
||||
if UISceneDelegateProto != null {
|
||||
class_addProtocol(SxSceneDelegate, UISceneDelegateProto);
|
||||
} else {
|
||||
NSLog(ns_string("[sx] WARN: UISceneDelegate protocol not found (dead-stripped)\n".ptr));
|
||||
}
|
||||
if UIWindowSceneDelegateProto != null {
|
||||
class_addProtocol(SxSceneDelegate, UIWindowSceneDelegateProto);
|
||||
}
|
||||
|
||||
objc_registerClassPair(SxSceneDelegate);
|
||||
// SxAppDelegate (M3.1) + SxSceneDelegate (M3.2) are now
|
||||
// declarative `#objc_class(...)` blocks — the compiler
|
||||
// synthesises their IMPs, class-pair registration, and
|
||||
// protocol conformances at module init. The old hand-rolled
|
||||
// objc_allocateClassPair + class_addMethod + class_addProtocol
|
||||
// sequences are gone.
|
||||
|
||||
uikit_register_gl_view_class();
|
||||
uikit_register_metal_view_class();
|
||||
|
||||
@@ -654,6 +654,28 @@ pub const LLVMEmitter = struct {
|
||||
_ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, "");
|
||||
}
|
||||
|
||||
// M2.3 / M3.2 — register `#implements` protocol conformances
|
||||
// BEFORE objc_registerClassPair. iOS checks
|
||||
// `class_conformsToProtocol` when instantiating scene
|
||||
// delegates and other protocol-typed callbacks; without
|
||||
// these the runtime silently rejects the class.
|
||||
//
|
||||
// The protocol may not be present on every SDK / runtime
|
||||
// (dead-strip pruning, version skew), so `objc_getProtocol`
|
||||
// returning null is non-fatal — skip the addProtocol call.
|
||||
const get_proto_fn, const get_proto_ty = self.lazyDeclareCRuntime("objc_getProtocol", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0);
|
||||
const add_proto_fn, const add_proto_ty = self.lazyDeclareCRuntime("class_addProtocol", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, i8_ty, 0);
|
||||
for (fcd.members) |m| switch (m) {
|
||||
.implements => |proto_alias| {
|
||||
const proto_str_global = self.emitPrivateCString(proto_alias, "OBJC_PROTOCOL_NAME_");
|
||||
var gp_args: [1]c.LLVMValueRef = .{proto_str_global};
|
||||
const proto_val = c.LLVMBuildCall2(self.builder, get_proto_ty, get_proto_fn, &gp_args, 1, "proto");
|
||||
var ap_args: [2]c.LLVMValueRef = .{ cls_val, proto_val };
|
||||
_ = c.LLVMBuildCall2(self.builder, add_proto_ty, add_proto_fn, &ap_args, 2, "");
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
|
||||
// objc_registerClassPair(cls)
|
||||
var reg_args: [1]c.LLVMValueRef = .{cls_val};
|
||||
_ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, ®_args, 1, "");
|
||||
|
||||
@@ -878,3 +878,7 @@ entry:
|
||||
}
|
||||
|
||||
declare ptr @object_getClass(ptr)
|
||||
|
||||
declare ptr @objc_getProtocol(ptr)
|
||||
|
||||
declare i8 @class_addProtocol(ptr, ptr)
|
||||
|
||||
Reference in New Issue
Block a user