Adopted the standard withCString for C strings throughout the library.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user