diff --git a/Sources/PlaydateKit/Graphics/Classes/TileMap.swift b/Sources/PlaydateKit/Graphics/Classes/TileMap.swift index daa1003..cc7e72d 100644 --- a/Sources/PlaydateKit/Graphics/Classes/TileMap.swift +++ b/Sources/PlaydateKit/Graphics/Classes/TileMap.swift @@ -48,14 +48,22 @@ extension Graphics { /// Fills the tilemap with `indexes`, `rowWidth` tiles per row. The /// tilemap is resized to fit. - public func setTiles(_ indexes: [UInt16], rowWidth: Int) { - var indexes = indexes - indexes.withUnsafeMutableBufferPointer { buffer in - tilemapAPI.pointee.setTiles.unsafelyUnwrapped(pointer, buffer.baseAddress, - Int32(buffer.count), Int32(rowWidth)) + public func setTiles(_ indexes: Span, rowWidth: Int) { + indexes.withUnsafeBufferPointer { buffer in + // The C API takes a non-const pointer but only reads the + // indexes, copying them into the tilemap. + tilemapAPI.pointee.setTiles.unsafelyUnwrapped( + pointer, UnsafeMutablePointer(mutating: buffer.baseAddress), + Int32(buffer.count), Int32(rowWidth)) } } + /// Fills the tilemap with `indexes`, `rowWidth` tiles per row. The + /// tilemap is resized to fit. + public func setTiles(_ indexes: [UInt16], rowWidth: Int) { + indexes.withUnsafeBufferPointer { setTiles($0.span, rowWidth: rowWidth) } + } + /// Sets the tile index at position (x, y). public func setTile(x: Int, y: Int, index: UInt16) { tilemapAPI.pointee.setTileAtPosition.unsafelyUnwrapped(pointer, Int32(x), Int32(y), index) diff --git a/Tests/PlaydateKit/MockPlaydate.swift b/Tests/PlaydateKit/MockPlaydate.swift index 972bdae..f6b16e2 100644 --- a/Tests/PlaydateKit/MockPlaydate.swift +++ b/Tests/PlaydateKit/MockPlaydate.swift @@ -24,6 +24,7 @@ enum Mock { nonisolated(unsafe) static let soundEffectAPI = UnsafeMutablePointer.allocate(capacity: 1) nonisolated(unsafe) static let lfoAPI = UnsafeMutablePointer.allocate(capacity: 1) nonisolated(unsafe) static let delayLineAPI = UnsafeMutablePointer.allocate(capacity: 1) + nonisolated(unsafe) static let tilemapAPI = UnsafeMutablePointer.allocate(capacity: 1) nonisolated(unsafe) static let fileAPI = UnsafeMutablePointer.allocate(capacity: 1) nonisolated(unsafe) static let jsonAPI = UnsafeMutablePointer.allocate(capacity: 1) nonisolated(unsafe) static let apiStruct = UnsafeMutablePointer.allocate(capacity: 1) @@ -38,6 +39,9 @@ enum Mock { /// Caps the bytes a file read returns (0 = end of file, negative = /// error); `nil` fills the whole request. nonisolated(unsafe) static var fileReadLimit: Int32? + /// The index buffer and count last handed to `setTiles`. + nonisolated(unsafe) static var tilesPointer: UnsafeMutablePointer? + nonisolated(unsafe) static var tilesCount: Int32 = 0 /// Userdata stored per sprite / menu item, as the OS would keep it. nonisolated(unsafe) static var spriteUserdata: [OpaquePointer: UnsafeMutableRawPointer] = [:] nonisolated(unsafe) static var menuUserdata: [OpaquePointer: UnsafeMutableRawPointer] = [:] @@ -87,6 +91,8 @@ enum Mock { buttonState = (0, 0, 0) patternBytes = [] fileReadLimit = nil + tilesPointer = nil + tilesCount = 0 spriteUserdata = [:] menuUserdata = [:] menuCallback = nil @@ -174,6 +180,19 @@ enum Mock { private static func installGraphics() { gfxAPI.initialize(to: playdate_graphics()) + tilemapAPI.initialize(to: playdate_tilemap()) + gfxAPI.pointee.tilemap = UnsafePointer(tilemapAPI) + + tilemapAPI.pointee.newTilemap = { + Mock.record("newTilemap") + return Mock.fakePointer() + } + tilemapAPI.pointee.freeTilemap = { _ in Mock.record("freeTilemap") } + tilemapAPI.pointee.setTiles = { _, indexes, count, rowWidth in + Mock.record("setTiles(\(count),\(rowWidth))") + Mock.tilesPointer = indexes + Mock.tilesCount = count + } gfxAPI.pointee.fillRect = { x, y, width, height, color in if color > 3, let pattern = UnsafeRawPointer(bitPattern: color) { diff --git a/Tests/PlaydateKit/WrapperTests.swift b/Tests/PlaydateKit/WrapperTests.swift index f5b7434..6a19984 100644 --- a/Tests/PlaydateKit/WrapperTests.swift +++ b/Tests/PlaydateKit/WrapperTests.swift @@ -99,6 +99,16 @@ struct WrapperTests { #expect(visited == 1) } + @Test func tileMapSetTilesPassesTheCallersStorageWithoutCopying() { + let tileMap = Graphics.TileMap() + let indexes: [UInt16] = [1, 2, 3, 4, 5, 6] + tileMap.setTiles(indexes, rowWidth: 3) + + #expect(Mock.events.contains("setTiles(6,3)")) + let storage = indexes.withUnsafeBufferPointer { $0.baseAddress } + #expect(UnsafePointer(Mock.tilesPointer) == storage) + } + // MARK: Sprite @Test func spriteUserdataRecoversWrapperInCallbacks() {