Adopted the standard withCString for C strings throughout the library.

This commit is contained in:
2026-09-18 11:58:57 +02:00
parent 575165dec4
commit 89c8d7a04c
22 changed files with 71 additions and 115 deletions
@@ -9,7 +9,7 @@ extension File {
/// Opens the file at `path`.
public init(path: String, mode: Options) throws(PlaydateError) {
let pointer = path.withPlaydateCString {
let pointer = path.withCString {
fileAPI.pointee.open.unsafelyUnwrapped($0, mode.cValue)
}
guard let pointer else { throw lastFileError() }
@@ -71,8 +71,8 @@ extension File {
/// Writes the string's UTF-8 to the file. Returns the bytes written.
@discardableResult
public func write(_ string: String) throws(PlaydateError) -> Int {
let result = string.withPlaydateUTF8 { bytes, count in
fileAPI.pointee.write.unsafelyUnwrapped(pointer, bytes, UInt32(count))
let result = string.withCString { cString in
fileAPI.pointee.write.unsafelyUnwrapped(pointer, cString, UInt32(string.utf8.count))
}
if result < 0 { throw lastFileError() }
return Int(result)
+7 -7
View File
@@ -23,12 +23,12 @@ extension File {
_ each: (String) -> Void) throws(PlaydateError) {
let result = withoutActuallyEscaping(each) { each in
var callback = each
return path.withPlaydateCString { cPath in
return path.withCString { cPath in
withUnsafeMutablePointer(to: &callback) { callbackPointer in
fileAPI.pointee.listfiles.unsafelyUnwrapped(cPath, { cName, userdata in
guard let cName, let userdata else { return }
let each = userdata.assumingMemoryBound(to: ((String) -> Void).self).pointee
each(String(playdateCString: cName))
each(String(cString: cName))
}, callbackPointer, showHidden ? 1 : 0)
}
}
@@ -39,7 +39,7 @@ extension File {
/// Information about the file or directory at `path`.
public static func stat(_ path: String) throws(PlaydateError) -> Stat {
var stat = FileStat()
let result = path.withPlaydateCString { fileAPI.pointee.stat.unsafelyUnwrapped($0, &stat) }
let result = path.withCString { fileAPI.pointee.stat.unsafelyUnwrapped($0, &stat) }
if result != 0 { throw lastFileError() }
return Stat(
isDirectory: stat.isdir != 0,
@@ -51,14 +51,14 @@ extension File {
/// Creates a directory (and intermediate directories) in the Data directory.
public static func mkdir(_ path: String) throws(PlaydateError) {
let result = path.withPlaydateCString { fileAPI.pointee.mkdir.unsafelyUnwrapped($0) }
let result = path.withCString { fileAPI.pointee.mkdir.unsafelyUnwrapped($0) }
if result != 0 { throw lastFileError() }
}
/// Deletes the file or directory at `path`. Directories require
/// `recursive` to be deleted with their contents.
public static func unlink(_ path: String, recursive: Bool = false) throws(PlaydateError) {
let result = path.withPlaydateCString {
let result = path.withCString {
fileAPI.pointee.unlink.unsafelyUnwrapped($0, recursive ? 1 : 0)
}
if result != 0 { throw lastFileError() }
@@ -67,8 +67,8 @@ extension File {
/// Renames (moves) a file in the Data directory, overwriting any existing
/// file at the destination.
public static func rename(from: String, to: String) throws(PlaydateError) {
let result = from.withPlaydateCString { cFrom in
to.withPlaydateCString { cTo in
let result = from.withCString { cFrom in
to.withCString { cTo in
fileAPI.pointee.rename.unsafelyUnwrapped(cFrom, cTo)
}
}
@@ -26,7 +26,7 @@ extension Graphics {
/// Loads a bitmap from a file in the game's pdx or Data directory.
public convenience init(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let pointer = path.withPlaydateCString { gfx.pointee.loadBitmap.unsafelyUnwrapped($0, &error) }
let pointer = path.withCString { gfx.pointee.loadBitmap.unsafelyUnwrapped($0, &error) }
guard let pointer else { throw PlaydateError(cString: error) }
self.init(pointer: pointer, isOwned: true)
}
@@ -77,7 +77,7 @@ extension Graphics {
/// Replaces the bitmap's contents with the image at `path`.
public func load(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
path.withPlaydateCString { gfx.pointee.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) }
path.withCString { gfx.pointee.loadIntoBitmap.unsafelyUnwrapped($0, pointer, &error) }
cachedSize = nil
if let error { throw PlaydateError(cString: error) }
}
@@ -18,7 +18,7 @@ extension Graphics {
/// Loads an image table from a file.
public convenience init(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let pointer = path.withPlaydateCString { gfx.pointee.loadBitmapTable.unsafelyUnwrapped($0, &error) }
let pointer = path.withCString { gfx.pointee.loadBitmapTable.unsafelyUnwrapped($0, &error) }
guard let pointer else { throw PlaydateError(cString: error) }
self.init(pointer: pointer)
}
@@ -30,7 +30,7 @@ extension Graphics {
/// Replaces the table's contents with the image table at `path`.
public func load(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
path.withPlaydateCString { gfx.pointee.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) }
path.withCString { gfx.pointee.loadIntoBitmapTable.unsafelyUnwrapped($0, pointer, &error) }
if let error { throw PlaydateError(cString: error) }
}
@@ -16,7 +16,7 @@ extension Graphics {
/// Loads a font from a file.
public convenience init(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let pointer = path.withPlaydateCString { gfx.pointee.loadFont.unsafelyUnwrapped($0, &error) }
let pointer = path.withCString { gfx.pointee.loadFont.unsafelyUnwrapped($0, &error) }
guard let pointer else { throw PlaydateError(cString: error) }
self.init(pointer: pointer)
}
@@ -48,8 +48,8 @@ extension Graphics {
/// The width of `text` when drawn with this font.
public func textWidth(_ text: String, tracking: Int = 0) -> Int {
text.withPlaydateUTF8 { bytes, count in
Int(gfx.pointee.getTextWidth.unsafelyUnwrapped(pointer, bytes, count,
text.withCString { cString in
Int(gfx.pointee.getTextWidth.unsafelyUnwrapped(pointer, cString, text.utf8.count,
kUTF8Encoding, Int32(tracking)))
}
}
@@ -57,9 +57,9 @@ extension Graphics {
/// The height of `text` when wrapped to `maxWidth` with this font.
public func textHeight(_ text: String, maxWidth: Int, wrap: TextWrappingMode = .word,
tracking: Int = 0, extraLeading: Int = 0) -> Int {
text.withPlaydateUTF8 { bytes, count in
text.withCString { cString in
Int(gfx.pointee.getTextHeightForMaxWidth.unsafelyUnwrapped(
pointer, bytes, count, Int32(maxWidth), kUTF8Encoding,
pointer, cString, text.utf8.count, Int32(maxWidth), kUTF8Encoding,
wrap.cValue, Int32(tracking), Int32(extraLeading)))
}
}
@@ -18,7 +18,7 @@ extension Graphics {
/// Opens the .pdv file at `path`.
public convenience init(path: String) throws(PlaydateError) {
let pointer = path.withPlaydateCString { videoAPI.pointee.loadVideo.unsafelyUnwrapped($0) }
let pointer = path.withCString { videoAPI.pointee.loadVideo.unsafelyUnwrapped($0) }
guard let pointer else {
throw PlaydateError(message: "unable to load video: \(path)")
}
+4 -4
View File
@@ -238,8 +238,8 @@ extension Graphics {
/// Draws `text` at (x, y) using the current font. Returns the drawn width.
@discardableResult
public static func drawText(_ text: String, x: Int, y: Int) -> Int {
text.withPlaydateUTF8 { bytes, count in
Int(gfx.pointee.drawText.unsafelyUnwrapped(bytes, count,
text.withCString { cString in
Int(gfx.pointee.drawText.unsafelyUnwrapped(cString, text.utf8.count,
kUTF8Encoding, Int32(x), Int32(y)))
}
}
@@ -247,8 +247,8 @@ extension Graphics {
/// Draws `text` wrapped and aligned inside the given rectangle.
public static func drawText(_ text: String, x: Int, y: Int, width: Int, height: Int,
wrap: TextWrappingMode = .word, align: TextAlignment = .left) {
text.withPlaydateUTF8 { bytes, count in
gfx.pointee.drawTextInRect.unsafelyUnwrapped(bytes, count, kUTF8Encoding,
text.withCString { cString in
gfx.pointee.drawTextInRect.unsafelyUnwrapped(cString, text.utf8.count, kUTF8Encoding,
Int32(x), Int32(y), Int32(width), Int32(height),
wrap.cValue, align.cValue)
}
@@ -43,10 +43,10 @@ extension JSON {
/// Call before writing each table value.
public func addTableMember(name: String) {
name.withPlaydateUTF8 { bytes, count in
name.withCString { cString in
withUnsafeMutablePointer(to: &encoder) {
$0.pointee.addTableMember.unsafelyUnwrapped(
$0, bytes.assumingMemoryBound(to: CChar.self), Int32(count))
$0, cString, Int32(name.utf8.count))
}
}
}
@@ -80,10 +80,10 @@ extension JSON {
/// Writes a string value.
public func writeString(_ value: String) {
value.withPlaydateUTF8 { bytes, count in
value.withCString { cString in
withUnsafeMutablePointer(to: &encoder) {
$0.pointee.writeString.unsafelyUnwrapped(
$0, bytes.assumingMemoryBound(to: CChar.self), Int32(count))
$0, cString, Int32(value.utf8.count))
}
}
}
+1 -1
View File
@@ -103,7 +103,7 @@ extension JSON {
let unmanaged = Unmanaged.passUnretained(context)
var decoder = makeDecoder(context: unmanaged)
var outval = json_value()
let ok = jsonString.withPlaydateCString { cString in
let ok = jsonString.withCString { cString in
withExtendedLifetime(context) {
jsonAPI.pointee.decodeString.unsafelyUnwrapped(&decoder, cString, &outval) != 0
}
+6 -6
View File
@@ -30,7 +30,7 @@ extension Lua {
/// for namespacing, e.g. "mylib.myfunc").
public static func addFunction(_ function: CFunction, name: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let ok = name.withPlaydateCString {
let ok = name.withCString {
luaAPI.pointee.addFunction.unsafelyUnwrapped(function, $0, &error) != 0
}
if !ok { throw PlaydateError(cString: error) }
@@ -72,7 +72,7 @@ extension Lua {
retainedBuffers.append(UnsafeMutableRawPointer(constantsBuffer))
var error: UnsafePointer<CChar>?
let ok = name.withPlaydateCString {
let ok = name.withCString {
luaAPI.pointee.registerClass.unsafelyUnwrapped($0, registrationsBuffer,
values.isEmpty ? nil : constantsBuffer,
isStatic ? 1 : 0, &error) != 0
@@ -153,7 +153,7 @@ extension Lua {
var userdataObject: OpaquePointer?
// The C API takes a non-const class name but only reads it, so the
// stack copy can be passed with a mutating cast.
let object = type.withPlaydateCString { cType in
let object = type.withCString { cType in
luaAPI.pointee.getArgObject.unsafelyUnwrapped(
Int32(position), UnsafeMutablePointer(mutating: cType), &userdataObject)
}
@@ -197,7 +197,7 @@ extension Lua {
/// Pushes a string onto the stack.
public static func push(_ value: String) {
value.withPlaydateCString { luaAPI.pointee.pushString.unsafelyUnwrapped($0) }
value.withCString { luaAPI.pointee.pushString.unsafelyUnwrapped($0) }
}
/// Pushes raw bytes (which may contain embedded zeros) onto the stack
@@ -226,7 +226,7 @@ extension Lua {
valueCount: Int = 0) -> UDObject? {
// The C API takes a non-const class name but only reads it, so the
// stack copy can be passed with a mutating cast.
let pointer = type.withPlaydateCString { cType in
let pointer = type.withCString { cType in
luaAPI.pointee.pushObject.unsafelyUnwrapped(
object, UnsafeMutablePointer(mutating: cType), Int32(valueCount))
}
@@ -240,7 +240,7 @@ extension Lua {
/// first. Calling Lua from Swift has overhead; use sparingly.
public static func callFunction(_ name: String, argumentCount: Int = 0) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let ok = name.withPlaydateCString {
let ok = name.withCString {
luaAPI.pointee.callFunction.unsafelyUnwrapped($0, Int32(argumentCount), &error) != 0
}
if !ok { throw PlaydateError(cString: error) }
@@ -33,7 +33,7 @@ extension Network {
/// Opens a connection to `server`. Fails if access has not been
/// granted.
public init?(server: String, port: Int = 443, useSSL: Bool = true) {
let pointer = server.withPlaydateCString {
let pointer = server.withCString {
httpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL)
}
guard let pointer else { return nil }
@@ -84,8 +84,8 @@ extension Network {
/// Sends a GET request for `path`. `headers` are raw header lines
/// (e.g. "Accept: text/html\r\n").
public func get(path: String, headers: String = "") throws(NetError) {
let error = path.withPlaydateCString { cPath in
headers.withPlaydateCString { cHeaders in
let error = path.withCString { cPath in
headers.withCString { cHeaders in
httpAPI.pointee.get.unsafelyUnwrapped(pointer, cPath, cHeaders, headers.utf8.count)
}
}
@@ -94,8 +94,8 @@ extension Network {
/// Sends a POST request for `path` with the given body.
public func post(path: String, headers: String = "", body: [UInt8]) throws(NetError) {
let error = path.withPlaydateCString { cPath in
headers.withPlaydateCString { cHeaders in
let error = path.withCString { cPath in
headers.withCString { cHeaders in
body.withUnsafeBytes { bodyBuffer in
httpAPI.pointee.post.unsafelyUnwrapped(
pointer, cPath, cHeaders, headers.utf8.count,
@@ -110,9 +110,9 @@ extension Network {
/// Sends a request with an arbitrary HTTP method.
public func query(method: String, path: String, headers: String = "",
body: [UInt8] = []) throws(NetError) {
let error = method.withPlaydateCString { cMethod in
path.withPlaydateCString { cPath in
headers.withPlaydateCString { cHeaders in
let error = method.withCString { cMethod in
path.withCString { cPath in
headers.withCString { cHeaders in
body.withUnsafeBytes { bodyBuffer in
httpAPI.pointee.query.unsafelyUnwrapped(
pointer, cMethod, cPath, cHeaders, headers.utf8.count,
@@ -30,7 +30,7 @@ extension Network {
/// Creates a connection to `server`. Fails if access has not been
/// granted. Call `open(_:)` to connect.
public init?(server: String, port: Int, useSSL: Bool = true) {
let pointer = server.withPlaydateCString {
let pointer = server.withCString {
tcpAPI.pointee.newConnection.unsafelyUnwrapped($0, Int32(port), useSSL)
}
guard let pointer else { return nil }
+2 -2
View File
@@ -57,9 +57,9 @@ extension Network {
guard let userdata else { return }
Unmanaged<Box>.fromOpaque(userdata).takeRetainedValue().body(allowed)
}
let reply = server.withPlaydateCString { cServer in
let reply = server.withCString { cServer in
if let purpose {
return purpose.withPlaydateCString { cPurpose in
return purpose.withCString { cPurpose in
rawRequest(cServer, Int32(port), useSSL, cPurpose, trampoline, box.toOpaque())
}
} else {
@@ -22,7 +22,7 @@ extension Scoreboards {
public static func addScore(boardID: String, value: UInt32,
completion: @escaping (Result<Score, PlaydateError>) -> Void) -> Bool {
addScoreCompletion = completion
return boardID.withPlaydateCString { cBoardID in
return boardID.withCString { cBoardID in
scoreboardsAPI.pointee.addScore.unsafelyUnwrapped(cBoardID, value, { score, errorMessage in
let completion = Scoreboards.addScoreCompletion
Scoreboards.addScoreCompletion = nil
@@ -36,7 +36,7 @@ extension Scoreboards {
public static func getPersonalBest(boardID: String,
completion: @escaping (Result<Score, PlaydateError>) -> Void) -> Bool {
personalBestCompletion = completion
return boardID.withPlaydateCString { cBoardID in
return boardID.withCString { cBoardID in
scoreboardsAPI.pointee.getPersonalBest.unsafelyUnwrapped(cBoardID, { score, errorMessage in
let completion = Scoreboards.personalBestCompletion
Scoreboards.personalBestCompletion = nil
@@ -67,7 +67,7 @@ extension Scoreboards {
public static func getScores(boardID: String,
completion: @escaping (Result<ScoresList, PlaydateError>) -> Void) -> Bool {
scoresCompletion = completion
return boardID.withPlaydateCString { cBoardID in
return boardID.withCString { cBoardID in
scoreboardsAPI.pointee.getScores.unsafelyUnwrapped(cBoardID, { scores, errorMessage in
let completion = Scoreboards.scoresCompletion
Scoreboards.scoresCompletion = nil
+1 -1
View File
@@ -84,7 +84,7 @@ extension Sound {
}
let reply: accessReply
if let purpose {
reply = purpose.withPlaydateCString {
reply = purpose.withCString {
snd.pointee.requestMicAccess.unsafelyUnwrapped($0, trampoline, box.toOpaque())
}
} else {
@@ -21,7 +21,7 @@ extension Sound {
/// Loads the wav or aiff file at `path`.
public convenience init(path: String) throws(PlaydateError) {
let pointer = path.withPlaydateCString { AudioSample.api.pointee.load.unsafelyUnwrapped($0) }
let pointer = path.withCString { AudioSample.api.pointee.load.unsafelyUnwrapped($0) }
guard let pointer else {
throw PlaydateError(message: "unable to load sample: \(path)")
}
@@ -49,7 +49,7 @@ extension Sound {
/// Loads the file at `path` into this sample's buffer.
public func load(path: String) throws(PlaydateError) {
let loaded = path.withPlaydateCString {
let loaded = path.withCString {
AudioSample.api.pointee.loadIntoSample.unsafelyUnwrapped(pointer, $0) != 0
}
if !loaded {
@@ -33,7 +33,7 @@ extension Sound {
/// Prepares the player to stream the file at `path`.
public func load(path: String) throws(PlaydateError) {
let loaded = path.withPlaydateCString {
let loaded = path.withCString {
FilePlayer.api.pointee.loadIntoPlayer.unsafelyUnwrapped(pointer, $0) != 0
}
if !loaded {
@@ -25,7 +25,7 @@ extension Sound {
}
public func loadMIDIFile(path: String) throws(PlaydateError) {
let loaded = path.withPlaydateCString {
let loaded = path.withCString {
Sequence.api.pointee.loadMIDIFile.unsafelyUnwrapped(pointer, $0) != 0
}
if !loaded {
+8 -51
View File
@@ -2,67 +2,24 @@ internal import CPlaydate
/// Internal C-string helpers shared by the wrappers.
///
/// The conversions are implemented manually (rather than with
/// `String(cString:)` / `withCString`) so the module stays within the
/// Embedded Swift subset used for device builds.
/// Passing strings to C goes through the standard `withCString`, which hands
/// out a pointer to the string's own null-terminated storage without copying.
extension String {
/// Creates a string by copying a null-terminated UTF-8 C string.
init(playdateCString pointer: UnsafePointer<CChar>) {
var count = 0
while pointer[count] != 0 { count += 1 }
let bytes = UnsafeRawBufferPointer(start: pointer, count: count)
self = String(decoding: bytes, as: UTF8.self)
}
/// Creates a string from a nullable C string, or `nil` if the pointer is null.
init?(playdateCString pointer: UnsafePointer<CChar>?) {
guard let pointer else { return nil }
self.init(playdateCString: pointer)
}
/// Calls `body` with a temporary null-terminated UTF-8 copy of the
/// string. The copy lives on the stack for short strings, so calling
/// this in the update loop does not churn the heap.
func withPlaydateCString<Result>(_ body: (UnsafePointer<CChar>) -> Result) -> Result {
let count = utf8.count
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: count + 1) { buffer in
var index = 0
for byte in utf8 {
buffer[index] = CChar(bitPattern: byte)
index += 1
}
buffer[count] = 0
return body(buffer.baseAddress.unsafelyUnwrapped)
}
}
/// Calls `body` with a temporary buffer of the string's UTF-8 bytes (not
/// null-terminated) and its length, for the `(const void*, size_t)` text
/// APIs. Stack-allocated for short strings.
func withPlaydateUTF8<Result>(_ body: (UnsafeRawPointer, Int) -> Result) -> Result {
let count = utf8.count
return withUnsafeTemporaryAllocation(of: UInt8.self, capacity: count + 1) { buffer in
var index = 0
for byte in utf8 {
buffer[index] = byte
index += 1
}
return body(UnsafeRawPointer(buffer.baseAddress.unsafelyUnwrapped), count)
}
self.init(cString: pointer)
}
/// Copies the string into a newly allocated null-terminated C string.
/// The caller owns the memory and must free it with `deallocate()`.
func copiedPlaydateCString() -> UnsafeMutablePointer<CChar> {
let count = utf8.count
let buffer = UnsafeMutablePointer<CChar>.allocate(capacity: count + 1)
var index = 0
for byte in utf8 {
buffer[index] = CChar(bitPattern: byte)
index += 1
withCString { cString in
let count = utf8.count + 1
let buffer = UnsafeMutablePointer<CChar>.allocate(capacity: count)
buffer.initialize(from: cString, count: count)
return buffer
}
buffer[count] = 0
return buffer
}
}
@@ -28,7 +28,7 @@ extension System {
String(playdateCString: Playdate.systemAPI.pointee.getMenuItemTitle.unsafelyUnwrapped(pointer)) ?? ""
}
set {
newValue.withPlaydateCString {
newValue.withCString {
Playdate.systemAPI.pointee.setMenuItemTitle.unsafelyUnwrapped(pointer, $0)
}
}
+7 -7
View File
@@ -25,12 +25,12 @@ extension System {
/// Logs a message to the console (device serial or simulator console).
public static func log(_ message: String) {
message.withPlaydateCString { cplaydate_log(Playdate.apiPointer, $0) }
message.withCString { cplaydate_log(Playdate.apiPointer, $0) }
}
/// Stops execution and displays the message as a fatal error.
public static func error(_ message: String) {
message.withPlaydateCString { cplaydate_error(Playdate.apiPointer, $0) }
message.withCString { cplaydate_error(Playdate.apiPointer, $0) }
}
// MARK: - Time
@@ -208,7 +208,7 @@ extension System {
@discardableResult
public static func addMenuItem(title: String, onSelect: @escaping (MenuItem) -> Void) -> MenuItem? {
var item: MenuItem?
title.withPlaydateCString { cTitle in
title.withCString { cTitle in
let pointer = api.pointee.addMenuItem.unsafelyUnwrapped(cTitle, menuItemTrampoline, nil)
item = MenuItem(pointer: pointer, onSelect: onSelect)
}
@@ -220,7 +220,7 @@ extension System {
public static func addCheckmarkMenuItem(title: String, isChecked: Bool = false,
onSelect: @escaping (MenuItem) -> Void) -> MenuItem? {
var item: MenuItem?
title.withPlaydateCString { cTitle in
title.withCString { cTitle in
let pointer = api.pointee.addCheckmarkMenuItem.unsafelyUnwrapped(
cTitle, isChecked ? 1 : 0, menuItemTrampoline, nil)
item = MenuItem(pointer: pointer, onSelect: onSelect)
@@ -237,7 +237,7 @@ extension System {
let copies = options.map { $0.copiedPlaydateCString() }
var cOptions: [UnsafePointer<CChar>?] = copies.map { UnsafePointer($0) }
var item: MenuItem?
title.withPlaydateCString { cTitle in
title.withCString { cTitle in
cOptions.withUnsafeMutableBufferPointer { buffer in
let pointer = api.pointee.addOptionsMenuItem.unsafelyUnwrapped(
cTitle, buffer.baseAddress, Int32(options.count), menuItemTrampoline, nil)
@@ -291,7 +291,7 @@ extension System {
/// Quits the current game and restarts it with the given launch arguments.
public static func restartGame(launchArguments: String? = nil) {
if let launchArguments {
launchArguments.withPlaydateCString { api.pointee.restartGame.unsafelyUnwrapped($0) }
launchArguments.withCString { api.pointee.restartGame.unsafelyUnwrapped($0) }
} else {
api.pointee.restartGame.unsafelyUnwrapped(nil)
}
@@ -321,7 +321,7 @@ extension System {
/// Looks up a localized string by key from the game's strings files.
public static func localizedText(forKey key: String, language: Language = .system) -> String? {
key.withPlaydateCString { cKey in
key.withCString { cKey in
guard let cString = api.pointee.getLocalizedText.unsafelyUnwrapped(cKey, language.cValue) else {
return nil
}
+1 -2
View File
@@ -69,8 +69,7 @@ import Testing
defer { copy.deallocate() }
#expect(String(playdateCString: copy) == original)
let viaClosure = original.withPlaydateCString { String(playdateCString: $0) }
#expect(viaClosure == original)
#expect(String(playdateCString: nil) == nil)
}
@Test func dateTimeMirrorsCStruct() {