diff --git a/Examples/HelloPlaydate/README.md b/Examples/HelloPlaydate/README.md index 1f72997..d1e1d6f 100644 --- a/Examples/HelloPlaydate/README.md +++ b/Examples/HelloPlaydate/README.md @@ -18,6 +18,21 @@ The script compiles the game as a dylib, places it in `Source/` next to ## Device builds -Running on hardware requires the Embedded Swift + ARM toolchain pipeline -(see the repository README and Apple's swift-playdate-examples for the -Makefile setup). This example covers the simulator workflow only. +The Makefile cross-compiles the game with Embedded Swift and packages a +`HelloPlaydate.pdx` containing both the device binary and the simulator +dylib. It needs: + +- the Playdate SDK (`PLAYDATE_SDK_PATH`, or the path in `~/.Playdate/config`), +- the Arm GNU toolchain (`arm-none-eabi-gcc`) on `PATH`, +- a swift.org development-snapshot toolchain (Xcode's toolchain does not + ship the Embedded Swift stdlib for the device target). A toolchain + installed at `~/Library/Developer/Toolchains` is picked up automatically; + otherwise pass `TOOLCHAINS=`. + +```sh +make +``` + +Swift code is compiled with `-Osize` by default; use `make SWIFT_OPT=-O` to +favor speed over size. Sideload the pdx from the Playdate Simulator +(Device > Upload Game to Device) or with the SDK's `pdutil`. diff --git a/Examples/HelloPlaydate/Source/pdex.elf b/Examples/HelloPlaydate/Source/pdex.elf index c35103c..60986fe 100755 Binary files a/Examples/HelloPlaydate/Source/pdex.elf and b/Examples/HelloPlaydate/Source/pdex.elf differ diff --git a/Examples/swift.mk b/Examples/swift.mk index a050895..3cd2c60 100644 --- a/Examples/swift.mk +++ b/Examples/swift.mk @@ -4,7 +4,7 @@ STACK_SIZE = 61800 # Locate the Playdate SDK SDK = ${PLAYDATE_SDK_PATH} ifeq ($(SDK),) -SDK = $(shell egrep '^\s*SDKRoot' ~/.Playdate/config | head -n 1 | cut -c9-) +SDK = $(shell sed -nE 's/^[[:space:]]*SDKRoot[[:space:]]+//p' ~/.Playdate/config | head -n 1) endif ifeq ($(SDK),) @@ -28,18 +28,37 @@ else $(error Swift toolchain not found; set ENV value TOOLCHAINS (e.g. TOOLCHAINS=org.swift.59202403121a make)) endif +# Note: paths containing spaces are not supported (make word-splits them). GCC_INCLUDE_PATHS := $(shell $(CC) -E -Wp,-v -xc /dev/null 2>&1 | egrep '^ ' | xargs echo ) +ifeq ($(GCC_INCLUDE_PATHS),) +$(error arm-none-eabi C headers not found; install the Arm GNU toolchain and ensure $(CC) is runnable) +endif + SWIFT_EXEC := "$(shell TOOLCHAINS=$(TOOLCHAINS) xcrun -f swiftc)" +# With an unknown TOOLCHAINS id, xcrun silently falls back to Xcode's swiftc, +# which lacks the Embedded ARM stdlib; fail early with a clear message. +ifeq ($(findstring .xctoolchain,$(SWIFT_EXEC)),) +$(error swiftc for toolchain "$(TOOLCHAINS)" not found (xcrun returned $(SWIFT_EXEC)); install a swift.org toolchain or set TOOLCHAINS) +endif TOOLCHAIN_PATH := $(shell echo $(SWIFT_EXEC)|sed s'/.xctoolchain.*/.xctoolchain/') $(info Using Swift toolchain "$(TOOLCHAINS)" (from $(TOOLCHAIN_PATH))) +# Optimization mode for Swift compiles. -Osize targets small code, which +# suits the device's flash/RAM budget (measured 15% smaller than -O on the +# HelloPlaydate example); override per-build for maximum speed instead: +# `make SWIFT_OPT=-O`. For larger games, appending +# `-Xfrontend -mergeable-symbols -Xfrontend -mergeable-traps` lets the +# linker deduplicate stdlib code specialized into both the wrapper and the +# game module. +SWIFT_OPT ?= -Osize + C_FLAGS := \ $(addprefix -I ,$(GCC_INCLUDE_PATHS)) \ SWIFT_FLAGS := \ $(addprefix -Xcc , $(C_FLAGS)) \ - -O \ + $(SWIFT_OPT) \ -wmo -enable-experimental-feature Embedded \ -Xfrontend -disable-stack-protector \ -Xfrontend -function-sections \ diff --git a/Scripts/build-embedded.sh b/Scripts/build-embedded.sh index 9084622..d75769d 100755 --- a/Scripts/build-embedded.sh +++ b/Scripts/build-embedded.sh @@ -50,6 +50,7 @@ exec "$swift_bin" build \ --triple armv7em-none-none-eabi \ -Xswiftc -enable-experimental-feature -Xswiftc Embedded \ -Xswiftc -wmo \ + -Xswiftc -Osize \ -Xcc -I"$include_dir" \ -Xcc -mcpu=cortex-m7 \ -Xcc -mfloat-abi=hard \ diff --git a/Sources/PlayDate/GraphicsVideo.swift b/Sources/PlayDate/GraphicsVideo.swift index 5f65b5f..eb71767 100644 --- a/Sources/PlayDate/GraphicsVideo.swift +++ b/Sources/PlayDate/GraphicsVideo.swift @@ -60,7 +60,10 @@ extension Graphics { /// Renders frame `frame` into the current context. public func renderFrame(_ frame: Int) throws(PlaydateError) { guard videoAPI.pointee.renderFrame.unsafelyUnwrapped(pointer, Int32(frame)) != 0 else { - throw PlaydateError(message: error ?? "unable to render frame \(frame)") + // Static message: the caller knows the frame it passed, and + // interpolating it would pull integer formatting machinery + // into the device binary. + throw PlaydateError(message: error ?? "unable to render frame") } } diff --git a/Sources/PlayDate/JSON.swift b/Sources/PlayDate/JSON.swift index 9af6f13..2590a66 100644 --- a/Sources/PlayDate/JSON.swift +++ b/Sources/PlayDate/JSON.swift @@ -170,8 +170,10 @@ extension JSON { return try decode(file: file) } + // Static message: interpolating the line number would pull integer + // formatting machinery into every device binary that decodes JSON. private static func decodeError(_ context: DecodeContext) -> PlaydateError { - PlaydateError(message: "\(context.errorMessage ?? "JSON decode failed") (line \(context.errorLine))") + PlaydateError(message: context.errorMessage ?? "JSON decode failed") } // MARK: - Encoding