Playdate Nim bindings, aiming to provide:
- C comparable performance
- Concise and easy syntax
- More ergonomic API over the C API
- Automatic memory management
The main takeaway is that, while this is to some extent a 1 to 1 mapping of the C API, it also adds extra features such as reference counted memory, OOP syntax, many other nice to haves and enhanced developer experience.
The bindings are perfectly usable but a few breaking changes are expected on the road to v1.0.
Here's a quick comparison between the main languages usable on the Playdate:
Language | C | Lua | Nim |
---|---|---|---|
Performance | ✔ Great | Decent | ✔ Great |
Memory Management | ✖ No management | ✔ Garbage collected | ✔ Reference counted |
Memory usage | ✔ Lowest | Acceptable | ✔ Low |
Typing | Static | Dynamic | Static |
API | Low level | High level | Mixed |
Syntax | Quite easy | ✔ Easiest & concise | ✔ Easy & concise |
Error checking | Basic compile + runtime | ✖ Mostly runtime | ✔ Compile time + runtime |
OOP | ✖ Not supported | ✔ Supported | ✔ Supported |
This package is an independent bindings library, not affiliated with Panic.
- Playdate SDK
- Nim 2+ (recommended extension for VSCode)
- Nimble 0.14.2+ (check with
nimble -v
) PLAYDATE_SDK_PATH
environment variable- SDK Prerequisites based on OS, and MinGW on Windows.
Make sure the gcc-arm-none-eabi
toolchain being used is the one shipped with the Playdate SDK.
If you're using a package manager like Homebrew, another gcc-arm-none-eabi
installation could shadow the SDK toolchain.
You can quickly start using the bindings opening the playdate_example
project included in this repository.
If you want to start from scratch, here are the steps to follow:
- If you haven't done it already, start creating a folder (snake_case) and initializing your nimble package inside it running:
nimble init
Choose binary
as the package type.
- Install the
playdate
package:
nimble install playdate
- Add the
playdate
package as a dependency and configure the build tasks by running the following:
echo 'requires "playdate"' >> *.nimble;
echo 'include playdate/build/nimble' >> *.nimble;
echo 'include playdate/build/config' > config.nims;
- Finally, run this command to setup the structure of the project, which prepares your application to be compiled and bundled correctly:
nimble configure
If you haven't done it already, install the package:
nimble install playdate
playdate_example
contains a basic example of the bindings utilization.
The example code is in playdate_example/src/playdate_example.nim
.
Here's also a minimal snippet to make a Nim application:
import playdate/api
var nimLogoBitmap: LCDBitmap
proc update(): int {.raises: [].} =
nimLogoBitmap.draw(168, 88, kBitmapUnflipped)
# This is the application entrypoint and event handler
proc handler(event: PDSystemEvent, keycode: uint) {.raises: [].} =
if event == kEventInit:
# Errors are handled through exceptions, this is an inline try/except
nimLogoBitmap = try: playdate.graphics.newBitmap("/images/nim_logo") except: nil
# playdate is the global PlaydateAPI instance, available when playdate/api is imported
playdate.system.setUpdateCallback(update)
# Used to setup the SDK entrypoint
initSDK()
Compile the project (pdx) for the simulator using:
nimble simulator
For the device (pdx):
nimble device
For simulator + device (pdx):
nimble all
You can also build for simulator and launch it in one command:
nimble simulate
The example project playdate_example
also contains VSCode launch configurations to build, start and debug your Nim application from the editor.
Each project contains a config.nims
file that can be edited to customize how the project should be built, e.g. adding libraries or other external code.
Here's an example of a config.nims
that links a pre-built static library called chipmunk:
include playdate/build/config
# Add a search path for libraries based on OS.
if defined(device):
switch("passL", "-L" & getCurrentDir() / "lib" / "device")
elif defined(windows):
switch("passL", "-L" & getCurrentDir() / "lib" / "windows")
elif defined(macosx):
switch("passL", "-L" & getCurrentDir() / "lib" / "macos")
elif defined(linux):
switch("passL", "-L" & getCurrentDir() / "lib" / "linux")
else:
echo "Platform not supported!"
# Link the chipmunk library.
switch("passL", "-lchipmunk")
Nim can be used together with Lua. There are two ways you can use Nim and Lua in the same project:
- The main loop is defined in Nim, but you want to call a few Lua functions.
- The main loop is defined in Lua, but you want to call Nim functions.
Either way, you can provide Lua with your Nim functions during Lua initialization:
proc nimInsideLua(state: LuaStatePtr): cint {.cdecl, raises: [].} = ...
# Application entrypoint and event handler
proc handler(event: PDSystemEvent, keycode: uint) {.raises: [].} =
if event == kEventInitLua: # Lua initialization event
# Add a function `nimInsideLua` to the Lua environment
playdate.lua.addFunction(nimInsideLua, "nimInsideLua")
# If you want to use Nim to define the main loop, set the update callback
playdate.system.setUpdateCallback(update)
Calling a Lua function from Nim:
try:
# Push the argument first
playdate.lua.pushInt(5)
playdate.lua.callFunction("funcWithOneArgument", 1)
except:
playdate.system.logToConsole(getCurrentExceptionMsg())
This project is a work in progress, here's what is missing right now:
- various playdate.sound funcionalities (but FilePlayer, SamplePlayer and SoundSequence are available)
- playdate.json, but you can use Nim std/json, which is very convenient
- advanced playdate.lua features, but basic Lua interop is available
- playdate.scoreboards, undocumented even in the official C API docs