Files
playdate-kit/Sources/PlaydateKit/Network/Network.swift
T
javier c5037dd716 Swift 6.4 migration and exhancements (#1)
This PR contains the work done to update the library and the attached example project to use the Swift 6.4 computer as a minimum supported version and also, to use the latest features introduced in it.

Reviewed-on: #1
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
2026-09-18 13:15:08 +00:00

81 lines
3.3 KiB
Swift

internal import CPlaydate
/// The cached `playdate->network` C API table.
private var networkAPI: UnsafePointer<playdate_network> { Playdate.networkAPI.unsafelyUnwrapped }
/// Wifi control, HTTP, and TCP. Throwing APIs here throw `Network.NetError`.
public enum Network {}
extension Network {
static func check(_ error: PDNetErr) throws(NetError) {
if error != NET_OK {
throw NetError(error)
}
}
static func optionalError(_ error: PDNetErr) -> NetError? {
error == NET_OK ? nil : NetError(error)
}
/// The current wifi status; `.notConnected` for unrecognized C values.
public static var status: WifiStatus {
WifiStatus(rawValue: UInt32(networkAPI.pointee.getStatus.unsafelyUnwrapped().rawValue)) ?? .notConnected
}
/// Connects to the access point now. `completion` gets `nil` on success, in call order.
public static func enable(completion: ((NetError?) -> Void)? = nil) {
if let completion {
enableCompletions.append(completion)
networkAPI.pointee.setEnabled.unsafelyUnwrapped(true, { error in
guard !Network.enableCompletions.isEmpty else { return }
let completion = Network.enableCompletions.removeFirst()
completion(Network.optionalError(error))
})
} else {
networkAPI.pointee.setEnabled.unsafelyUnwrapped(true, nil)
}
}
/// Turns wifi off now, not after the 30 s idle timeout.
public static func disable() {
// No callback: C documents it for enabling only, and a queued one would take
// the next `enable` result.
networkAPI.pointee.setEnabled.unsafelyUnwrapped(false, nil)
}
nonisolated(unsafe) private static var enableCompletions: [(NetError?) -> Void] = []
/// Shared by HTTP and TCP. Retains `completion` until the C callback, which
/// fires only for `.ask`.
static func requestAccess(
rawRequest: (UnsafePointer<CChar>?, Int32, Bool, UnsafePointer<CChar>?,
(@convention(c) (Bool, UnsafeMutableRawPointer?) -> Void)?,
UnsafeMutableRawPointer?) -> accessReply,
server: String, port: Int, useSSL: Bool, purpose: String?,
completion: @escaping (Bool) -> Void) -> AccessReply {
final class Box {
let body: (Bool) -> Void
init(_ body: @escaping (Bool) -> Void) { self.body = body }
}
let box = Unmanaged.passRetained(Box(completion))
let trampoline: @convention(c) (Bool, UnsafeMutableRawPointer?) -> Void = { allowed, userdata in
guard let userdata else { return }
Unmanaged<Box>.fromOpaque(userdata).takeRetainedValue().body(allowed)
}
let reply = server.withCString { cServer in
if let purpose {
return purpose.withCString { cPurpose in
rawRequest(cServer, Int32(port), useSSL, cPurpose, trampoline, box.toOpaque())
}
} else {
return rawRequest(cServer, Int32(port), useSSL, nil, trampoline, box.toOpaque())
}
}
if reply != kAccessAsk {
// Only `kAccessAsk` invokes the callback; balance the retain now.
box.release()
}
return AccessReply(rawValue: UInt32(reply.rawValue)) ?? .ask
}
}