Continuing the discussion from Byte-sized Swift: Building Tiny Games for the Playdate:
Good question.
The following is mostly coded here in the Forums, i.e. may not actually compile or execute correctly. I'm just theorising through how one might do this.
Let's start with a specific example line from your code:
if let buffer = playdate.pointee.graphics.pointee.getDisplayFrame() {
Here's a quick adaptation of what I typically use as a starting point:
@dynamicMemberLookup
final class API: @unchecked Sendable {
let contents: UnsafePointer<PlaydateAPI>
init(_ contents: UnsafePointer<PlaydateAPI>) {
self.contents = contents
}
subscript<T>(dynamicMember keyPath: KeyPath<PlaydateAPI, T>) -> T {
contents.pointee[keyPath: keyPath]
}
deinit {
// Here you could free the memory of the pointer,
// in other circumstances where you take ownership
// of it in `init`.
}
}
// So now you have:
if let buffer = playdate.graphics.pointee.getDisplayFrame() {
That gets rid of the first layer of .pointee.
. Xcode will even auto-complete the member namesÂą. It should optimise away completely in release builds.
But of course you have the problem that the returned values are all UnsafePointer
s themselves (based on what I assume is the correct API here - Panic don't appear to provide public access to their SDKs ).
But (I think) you could specialise that away with an additional overload:
subscript<T>(dynamicMember keyPath: KeyPath<PlaydateAPI, UnsafePointer<T>>) -> T {
contents.pointee[keyPath: keyPath].pointee
}
// So now you have:
if let buffer = playdate.graphics.getDisplayFrame() {
The big potential problem is that the above is at least nominally making copies of the members. Probably the optimiser would eliminate those, but it'd be nice if there were some way to ensure that, in the way it's written. I don't know if there is, off-hand. Maybe heavy use of @inline(__always)
might help?
The alternative would be to instead return a similar @dynamicMemberLookup
wrapper for the sub-structs, which will definitely avoid any copies of them, but I'm not sure what happens when you use getDisplayFrame
through dynamic member lookup. Arguably it should work, because it should be a lookup of the getDisplayFrame
member which is typed as a function (() -> UnsafePointer<UInt8>
) which you then simply invoke with ()
. But I'm not sure if that's how Swift imports the C APIs.
If it doesn't work that way, you could make your own wrappers utilising @dynamicCallable
, perhaps.
¹ …sometimes. I'm not certain if it's any less reliable than for any other case, as Xcode definitely sometimes just refuses to auto-complete or offer correct suggestions in general. But I have seen it refuse to see through the dynamic member lookup at random times, seeing only the concrete contents
member.