├── .gitignore ├── Example ├── .gitignore ├── Package.swift ├── Source │ ├── pdxinfo │ └── the-bird.png ├── Sources │ ├── Example │ │ ├── Game.swift │ │ └── Playdate │ └── Example_Simulator └── build.sh ├── Package.swift ├── README.md ├── Sources ├── Playdate │ └── Playdate.swift └── _CPlaydate │ ├── _CPlaydate.c │ └── include │ ├── _CPlaydate.h │ └── module.modulemap └── SwiftSDKs └── Playdate.artifactbundle ├── generic ├── bin │ └── playdate-ld ├── swift-sdk.json └── toolset.json └── info.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | pdex.* 10 | *.pdx 11 | -------------------------------------------------------------------------------- /Example/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10 2 | 3 | import PackageDescription 4 | import Foundation 5 | 6 | func defaultSDKPath() -> String { 7 | var config = try! String(contentsOfFile: "\(Context.environment["HOME"]!)/.Playdate/config") 8 | .split(separator: "\n").first! 9 | config.removeFirst("SDKRoot ".count) 10 | return String(config) 11 | } 12 | 13 | let PLAYDATE_SDK_PATH = Context.environment["PLAYDATE_SDK_PATH"] ?? defaultSDKPath() 14 | 15 | let package = Package( 16 | name: "Example", 17 | products: [ 18 | .executable(name: "Example", targets: ["Example"]), 19 | .library(name: "Example_Simulator", type: .dynamic, targets: ["Example_Simulator"]), 20 | ], 21 | dependencies: [ 22 | .package(path: ".."), 23 | ], 24 | targets: [ 25 | .executableTarget( 26 | name: "Example", 27 | swiftSettings: [ 28 | .enableExperimentalFeature("Embedded"), 29 | .unsafeFlags(["-I", "../Sources/_CPlaydate"]), 30 | .unsafeFlags([ 31 | "-Xfrontend", "-disable-stack-protector", 32 | "-Xfrontend", "-experimental-platform-c-calling-convention=arm_aapcs_vfp", 33 | "-Xcc", "-DTARGET_EXTENSION=1", 34 | "-Xcc", "-DTARGET_PLAYDATE=1", 35 | "-Xcc", "-D__FPU_USED=1", 36 | "-Xcc", "-I\(PLAYDATE_SDK_PATH)/C_API", 37 | "-Xcc", "-I/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/arm-none-eabi/include", 38 | "-Xcc", "-falign-functions=16", 39 | "-Xcc", "-fshort-enums", 40 | "-Xcc", "-mcpu=cortex-m7", 41 | "-Xcc", "-mfloat-abi=hard", 42 | "-Xcc", "-mfpu=fpv5-sp-d16", 43 | "-Xcc", "-mthumb", 44 | ]), 45 | ]), 46 | .target( 47 | name: "Example_Simulator", 48 | swiftSettings: [ 49 | .enableExperimentalFeature("Embedded", .when(platforms: [.custom("none")])), 50 | .unsafeFlags(["-I", "../Sources/_CPlaydate"]), 51 | .unsafeFlags([ 52 | "-Xcc", "-I\(PLAYDATE_SDK_PATH)/C_API", 53 | "-Xcc", "-DTARGET_EXTENSION=1", 54 | "-Xcc", "-DTARGET_SIMULATOR=1", 55 | ]), 56 | ]), 57 | ] 58 | ) 59 | -------------------------------------------------------------------------------- /Example/Source/pdxinfo: -------------------------------------------------------------------------------- 1 | name=Example 2 | author=Yuta Saito 3 | description= 4 | bundleID=dev.katei.pd.example 5 | imagePath= 6 | -------------------------------------------------------------------------------- /Example/Source/the-bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kateinoigakukun/swift-playdate/15760ca95260f445703f3e97526e87fb80520fae/Example/Source/the-bird.png -------------------------------------------------------------------------------- /Example/Sources/Example/Game.swift: -------------------------------------------------------------------------------- 1 | @_exported import _CPlaydate 2 | #if os(macOS) 3 | import Darwin 4 | #endif 5 | 6 | struct Context { 7 | let playdate: UnsafeMutablePointer 8 | } 9 | 10 | typealias Vector = SIMD2 11 | 12 | extension Vector { 13 | func collisioned(normal: Vector) -> Vector { 14 | self - (2.0 * self.innerProduct(normal)) * normal 15 | } 16 | func innerProduct(_ other: Vector) -> Float32 { 17 | self.x * other.x + self.y * other.y 18 | } 19 | 20 | init(radius: Float32, theta: Float32) { 21 | self = .init(radius * cosf(theta), radius * sinf(theta)) 22 | } 23 | } 24 | 25 | struct Game { 26 | let bird: Sprite 27 | var velocity: Vector 28 | 29 | init() { 30 | let image = Image(path: "the-bird.png") 31 | let bird = Sprite(image: image) 32 | bird.collideRect = .init(x: 0.0, y: 0.0, width: 85.0, height: 85.0) 33 | bird.add() 34 | 35 | self.velocity = .init(radius: 5.0, theta: -.pi * 0.4) 36 | 37 | self.bird = bird 38 | self.bird.move(toX: Float(LCD_COLUMNS / 2), y: Float(3 * LCD_ROWS / 4)) 39 | self.bird.setCollisionResponseFunction { _, _ in 40 | return kCollisionTypeBounce 41 | } 42 | self.bird.setUpdateFunction { handle in 43 | let bird = Sprite(borrowing: handle!) 44 | let bounds = bird.position 45 | let newX = game.velocity.x + bounds.x 46 | let newY = game.velocity.y + bounds.y 47 | var conflicts = 0 48 | bird.moveWithCollisions(goalX: newX, y: newY).forEach { collision in 49 | let normal = Vector( 50 | Float32(collision.normal.x), 51 | Float32(collision.normal.y) 52 | ) 53 | conflicts += 1 54 | game.velocity = game.velocity.collisioned(normal: normal) 55 | } 56 | } 57 | 58 | self.setupWalls() 59 | Sprite.draw() 60 | } 61 | 62 | func setupWalls() { 63 | let bar: Float = 10.0 64 | let walls: [PDRect] = [ 65 | .init(x: 0.0, y: 0.0, width: Float(LCD_COLUMNS), height: bar), 66 | .init(x: 0.0, y: 0.0, width: bar, height: Float(LCD_ROWS)), 67 | .init(x: (Float(LCD_COLUMNS) - bar) / 2.0, y: 0.0, width: bar, height: Float(LCD_ROWS)), 68 | .init(x: 0.0, y: (Float(LCD_ROWS) - bar) / 2.0, width: Float(LCD_COLUMNS), height: bar), 69 | ] 70 | 71 | for wall in walls { 72 | let sprite = Sprite() 73 | let bounds = PDRect(x: wall.x, y: wall.y, width: wall.width, height: wall.height) 74 | sprite.bounds = bounds 75 | sprite.collideRect = bounds 76 | sprite.setCollisionResponseFunction { _, _ in kCollisionTypeBounce } 77 | sprite.add() 78 | } 79 | } 80 | } 81 | var game: Game! 82 | 83 | @_cdecl("update") 84 | func update(playdate: UnsafeMutableRawPointer!) -> Int32 { 85 | Sprite.updateAndDrawSprites() 86 | return 1 87 | } 88 | 89 | @_cdecl("eventHandler") 90 | public func eventHandler( 91 | playdate: UnsafeMutablePointer, 92 | event: PDSystemEvent, 93 | arg: UInt32 94 | ) -> Int32 { 95 | switch event { 96 | case kEventInit: 97 | playdateAPI = playdate 98 | game = Game() 99 | playdate.pointee.system.pointee.setUpdateCallback(update, playdate) 100 | default: 101 | break 102 | } 103 | return 0 104 | } 105 | 106 | var playdateAPI: UnsafeMutablePointer! 107 | 108 | struct Image { 109 | let handle: OpaquePointer 110 | 111 | init(path: StaticString) { 112 | self.handle = path.withUTF8Buffer { 113 | playdateAPI.pointee.graphics.pointee.loadBitmap($0.baseAddress, nil)! 114 | } 115 | } 116 | } 117 | 118 | struct Sprite { 119 | let handle: OpaquePointer 120 | var bounds: PDRect { 121 | get { 122 | playdateAPI.pointee.sprite.pointee.getBounds(self.handle) 123 | } 124 | nonmutating set { 125 | playdateAPI.pointee.sprite.pointee.setBounds(self.handle, newValue) 126 | } 127 | } 128 | 129 | var position: (x: Float, y: Float) { 130 | var x: Float = 0.0, y: Float = 0.0 131 | playdateAPI.pointee.sprite.pointee.getPosition(self.handle, &x, &y) 132 | return (x, y) 133 | } 134 | 135 | var collideRect: PDRect { 136 | get { 137 | playdateAPI.pointee.sprite.pointee.getCollideRect(self.handle) 138 | } 139 | nonmutating set { 140 | playdateAPI.pointee.sprite.pointee.setCollideRect(self.handle, newValue) 141 | } 142 | } 143 | 144 | init() { 145 | self.handle = playdateAPI.pointee.sprite.pointee.newSprite()! 146 | } 147 | 148 | init(borrowing handle: OpaquePointer) { 149 | self.handle = handle 150 | } 151 | 152 | init(image: borrowing Image) { 153 | self.init() 154 | self.setImage(image) 155 | } 156 | 157 | func setImage(_ image: borrowing Image, flip: LCDBitmapFlip = kBitmapUnflipped) { 158 | playdateAPI.pointee.sprite.pointee.setImage(self.handle, image.handle, flip) 159 | } 160 | 161 | func move(toX x: Float, y: Float) { 162 | playdateAPI.pointee.sprite.pointee.moveTo(self.handle, x, y) 163 | } 164 | 165 | func add() { 166 | playdateAPI.pointee.sprite.pointee.addSprite(self.handle) 167 | } 168 | 169 | func setCollisionResponseFunction( 170 | _ body: @escaping @convention(c) (_ self: OpaquePointer?, _ other: OpaquePointer?) -> SpriteCollisionResponseType 171 | ) { 172 | playdateAPI.pointee.sprite.pointee.setCollisionResponseFunction(self.handle, body) 173 | } 174 | 175 | func setUpdateFunction( 176 | _ body: @convention(c) (OpaquePointer?) -> Void 177 | ) { 178 | playdateAPI.pointee.sprite.pointee.setUpdateFunction(self.handle, body) 179 | } 180 | 181 | struct Collisions: ~Copyable { 182 | let buffer: UnsafeBufferPointer 183 | deinit { buffer.deallocate() } 184 | 185 | consuming func forEach(_ body: (SpriteCollisionInfo) -> Void) { 186 | buffer.forEach(body) 187 | } 188 | } 189 | 190 | func moveWithCollisions( 191 | goalX x: Float, y: Float 192 | ) -> Collisions { 193 | var actualX: Float = 0.0 194 | var actualY: Float = 0.0 195 | var collisionCount: Int32 = 0 196 | let collisionBase = playdateAPI.pointee.sprite.pointee.moveWithCollisions( 197 | self.handle, x, y, &actualX, &actualY, &collisionCount 198 | ) 199 | return Collisions(buffer: UnsafeBufferPointer(start: collisionBase, count: Int(collisionCount))) 200 | } 201 | 202 | static func updateAndDrawSprites() { 203 | playdateAPI.pointee.sprite.pointee.updateAndDrawSprites() 204 | } 205 | 206 | static func draw() { 207 | playdateAPI.pointee.sprite.pointee.drawSprites() 208 | } 209 | } 210 | 211 | enum Display { 212 | static func setRefreshRate(_ rate: Float) { 213 | playdateAPI.pointee.display.pointee.setRefreshRate(rate) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Example/Sources/Example/Playdate: -------------------------------------------------------------------------------- 1 | ../../../Sources/Playdate -------------------------------------------------------------------------------- /Example/Sources/Example_Simulator: -------------------------------------------------------------------------------- 1 | ./Example -------------------------------------------------------------------------------- /Example/build.sh: -------------------------------------------------------------------------------- 1 | ### Device 2 | 3 | set -eux 4 | 5 | export TOOLCHAINS=org.swift.59202403011a 6 | 7 | swift-build --product Example --experimental-swift-sdk playdate -c release 8 | 9 | cp .build/armv7em-none-none-eabi/release/Example Source/pdex.elf 10 | 11 | ### Simulator 12 | 13 | swift-build --product Example_Simulator -c release 14 | cp .build/arm64-apple-macosx/release/libExample_Simulator.dylib Source/pdex.dylib 15 | 16 | pdc Source Example.pdx 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Playdate", 7 | products: [ 8 | .library( 9 | name: "Playdate", 10 | targets: ["Playdate"]), 11 | .library( 12 | name: "_CPlaydate", 13 | targets: ["_CPlaydate"]), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "Playdate", 18 | swiftSettings: [ 19 | .enableExperimentalFeature("Embedded"), 20 | ]), 21 | .target(name: "_CPlaydate"), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift on Playdate Demonstration 2 | 3 | https://github.com/kateinoigakukun/swift-playdate/assets/11702759/78dc07b4-168b-4d53-a779-0e45492b857f 4 | 5 | ## Want to build by yourself? 6 | 7 | 1. Install `swift-DEVELOPMENT-SNAPSHOT-2024-03-01-a` toolchain from [Swift.org](https://swift.org/download/) 8 | 2. Build SwiftPM with [a patch](https://github.com/apple/swift-package-manager/pull/7417), and include it in `PATH`. 9 | 10 | ```console 11 | $ swift experimental-sdk install ./SwiftSDKs/Playdate.artifactbundle 12 | $ cd Example 13 | $ ./build.sh 14 | $ open Example.pdx 15 | ``` 16 | -------------------------------------------------------------------------------- /Sources/Playdate/Playdate.swift: -------------------------------------------------------------------------------- 1 | @_exported import _CPlaydate 2 | #if !hasFeature(Embedded) 3 | import Darwin 4 | #endif 5 | 6 | @_cdecl("posix_memalign") 7 | public func posix_memalign( 8 | _ memptr: UnsafeMutablePointer, 9 | _ alignment: Int, 10 | _ size: Int 11 | ) -> CInt { 12 | guard let ptr = malloc(Int(size + alignment - 1)) else { 13 | fatalError() 14 | } 15 | memptr.pointee = ptr 16 | return 0 17 | } 18 | -------------------------------------------------------------------------------- /Sources/_CPlaydate/_CPlaydate.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kateinoigakukun/swift-playdate/15760ca95260f445703f3e97526e87fb80520fae/Sources/_CPlaydate/_CPlaydate.c -------------------------------------------------------------------------------- /Sources/_CPlaydate/include/_CPlaydate.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /Sources/_CPlaydate/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module _CPlaydate { 2 | header "_CPlaydate.h" 3 | } 4 | -------------------------------------------------------------------------------- /SwiftSDKs/Playdate.artifactbundle/generic/bin/playdate-ld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure PLAYDATE_SDK_PATH is available 4 | PLAYDATE_SDK_PATH=${PLAYDATE_SDK_PATH:-$(egrep '^\s*SDKRoot' ~/.Playdate/config | head -n 1 | cut -c9-)} 5 | if [ -z "$PLAYDATE_SDK_PATH" ]; then 6 | echo "PLAYDATE_SDK_PATH is not set. Please set it to the root of the Playdate SDK." 7 | exit 1 8 | fi 9 | 10 | OBJS="" 11 | OUTFILE="" 12 | while [ $# -gt 0 ]; do 13 | case "$1" in 14 | -o) 15 | shift 16 | OUTFILE="$1" 17 | ;; 18 | *.o) 19 | OBJS="$OBJS $1" 20 | ;; 21 | esac 22 | shift 23 | done 24 | 25 | tmpdir="$(mktemp -d /tmp/playdate-ld.XXXXXX)" 26 | 27 | /usr/local/bin/arm-none-eabi-gcc -g3 -c -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D__FPU_USED=1 -O2 -falign-functions=16 -fomit-frame-pointer -gdwarf-2 -Wall -Wno-unused -Wstrict-prototypes -Wno-unknown-pragmas -fverbose-asm -Wdouble-promotion -mword-relocations -fno-common -ffunction-sections -fdata-sections -Wa,-ahlms=build/setup.lst -DTARGET_PLAYDATE=1 -DTARGET_EXTENSION=1 -I . -I . -I "$PLAYDATE_SDK_PATH/C_API" "$PLAYDATE_SDK_PATH/C_API/buildsupport/setup.c" -o $tmpdir/setup.o 28 | 29 | exec /usr/local/bin/arm-none-eabi-gcc -g3 $tmpdir/setup.o $OBJS -nostartfiles -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D__FPU_USED=1 -T/Users/katei/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld -Wl,--gc-sections,--no-warn-mismatch,--emit-relocs -o $OUTFILE 30 | -------------------------------------------------------------------------------- /SwiftSDKs/Playdate.artifactbundle/generic/swift-sdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "targetTriples" : { 3 | "armv7em-none-none-eabi" : { 4 | "sdkRootPath" : "/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major", 5 | "toolsetPaths" : ["toolset.json"] 6 | } 7 | }, 8 | "schemaVersion" : "4.0" 9 | } 10 | -------------------------------------------------------------------------------- /SwiftSDKs/Playdate.artifactbundle/generic/toolset.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootPath" : "bin", 3 | "cCompiler" : { 4 | "path": "/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gcc", 5 | "extraCLIOptions" : [] 6 | }, 7 | "linker" : { 8 | "path": "playdate-ld", 9 | "extraCLIOptions" : [] 10 | }, 11 | "schemaVersion" : "1.0" 12 | } 13 | -------------------------------------------------------------------------------- /SwiftSDKs/Playdate.artifactbundle/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifacts" : { 3 | "playdate" : { 4 | "type" : "swiftSDK", 5 | "version" : "0.0.1", 6 | "variants" : [ 7 | { 8 | "path" : "./generic", 9 | "supportedTriples" : [ 10 | "arm64-apple-macosx13.0" 11 | ] 12 | } 13 | ] 14 | } 15 | }, 16 | "schemaVersion" : "1.0" 17 | } 18 | --------------------------------------------------------------------------------