Golang for Playdate handheld. Compiler, SDK Bindings, Tools and Examples

Hello dear Golang community!

My name is Roman, today in this thread I’m very excited to share my open-source project related to Go and game development which I released a few days ago. It’s still under active development, but is ready for a first public release.

This project called PdGo. It allows you to write games for Playdate in Golang.

Finally, cute yellow Playdate handheld from Panic Inc meets the Golang programming language, thanksfully to TinyGo!

Playdate Dev Forum DevLog: https://devforum.play.date/t/golang-support-for-playdate-compiler-sdk-bindings-tools-and-examples/24919

Project repository: https://github.com/playdate-go/pdgo

If it’s interesting for you I would be happy to discuss. In this thread I will periodically post project progress.

1 Like

Hi there! v0.2.0 Realeased!

Finally the new architecture for the project! Thanks for the guys from TinyGo community, they were surprised that TinyGo can’t use CGO, since that’s what I’d claimed, and I’m really glad they pointed it out - now the project relies entirely on CGO!

refactor: Unified CGO architecture for device and simulator builds.

Major refactoring of pdgo library to use direct CGO calls instead of //go:linkname bridging mechanism.

Key changes:

Single C source file (pd_cgo.c)

  • All Playdate SDK C wrappers in one file

  • Conditional compilation with #ifdef TARGET_PLAYDATE for device-specific code (ARM interrupts, TinyGo runtime)

  • Device entry point (eventHandler) included under TARGET_PLAYDATE

  • Simulator uses pd_set_api() for initialization

Removed file duplication

  • Deleted *_tinygo.go suffixes and build tags

  • Removed bridge_tinygo.go (old //go:linkname mechanism)

  • Removed embedded pd_runtime.go from pdgoc (was duplicating pd_cgo.c)

pdgoc build tool improvements

  • Dynamically reads pd_cgo.c from pdgo module via go list

  • Compiles with -DTARGET_PLAYDATE=1 for device builds

  • Single source of truth - no more maintaining two C files

Sprite callbacks support

  • Added pd_sprite_setDrawFunction, pd_sprite_setUpdateFunction, pd_sprite_setCollisionResponseFunction

  • Go trampolines (pdgo_sprite_*_trampoline) for C-to-Go callbacks

Update callback architecture

  • pdgo.SetUpdateCallback() registers callback via pd_sys_setUpdateCallback()

  • C wrapper update_callback_wrapper calls pdgo_update_trampoline

  • Works for both device and simulator

Benefits

  • Adding new Playdate SDK functions requires editing only pd_cgo.c

  • Unified codebase for device (TinyGo) and simulator (standard Go CGO)

  • Cleaner architecture without //go:linkname hacks

What? The guys from the TinyGo community were “surprised that TinyGo can’t use CGO”? Gonna need more explanation here. Where exactly did this conversation take place? Can you link me an issue on the TinyGo project?

Hi! I think I wrote that a bit unclearly. The first versions of PdGo before architecture refactoring didn’t use CGO directly because I couldn’t get TinyGo working with CGO for Playdate… I even wrote it in the README - “unfortunately TinyGo doesn’t support CGO for Playdate and due to this reason we need to use workaround.” The TinyGo folks saw it, told me that’s not true, and were pretty surprised by my claim. After that, I did CGO end up working finally for Playdate, and now the project relies entirely on it :+1: This conversation was on the Slack.

it was my mistake and I just didn’t know how to set it up at first, but then I figured it out.

Hey everyone! :waving_hand:

PdGo v0.3.0 just dropped!


So what’s new ? Let me walk you through it:

:bug: Bug Fixes

The dreaded runtime_init error is gone!
If you’ve ever seen undefined reference to 'runtime_init' after updating TinyGo - yeah, that was annoying. Turns out we needed to explicitly export this function from the Go runtime. Fixed now, builds work smoothly again.

macOS disk eject actually works now
Some of you with CleanMyMac (or similar tools) might have noticed -deploy failing with “Unmount was dissented by PID…”. The fix? If the normal eject doesn’t work, we now force it.


:sparkles: New Features

-run flag got way more flexible!

Build it, run it - in one command:

  • -sim -run → build + launch simulator
  • -device -deploy → build + push to device + run
  • -sim -device -run -deploy → do it all at once

No more manual steps. Just add -run or -deploy and watch it go.


:books: Docs Cleanup

  • Merged all the setup instructions into Quick Install - one place, one command: curl | bash
  • Removed that old build-tinygo-playdate.sh script -install.sh handles everything now
  • TinyGo version is locked to 0.40.1 - stability first

As always, feedback and PRs are welcome. Happy coding! :video_game:

Hello guys! PdGo v0.4.0 Released!

JSON API: The Deep Dive into Playdate’s JSON

Finally got JSON working on the Playdate! But man, this was NOT straightforward. Here’s the story.

The Problem

Playdate SDK has a JSON API, but it’s callback-based. Not like Go’s nice json.Unmarshal() - nope, you get bombarded with callbacks: “hey I found a key!”, “hey I’m entering an object!”, “hey here’s a value!”. Fun times.

My first instinct? Just use Go’s encoding/json. One line, done, right?

WRONG.

Turns out encoding/json uses reflection internally. Reflection can panic. Panic needs longjmp. TinyGo on bare metal doesn’t have that. Game over.

The Journey

Attempt 1: Use encoding/json anyway

  • Result: Linker error. No tinygo_longjmp symbol.
  • Why: Go’s reflection-based JSON uses panic/recover which TinyGo can’t do on Cortex-M7.

Attempt 2: Wrap Playdate’s callback API

  • Challenge: C callbacks calling Go functions. On bare metal. Without proper stack.
  • Solution: CGO trampolines. Go exports functions, C calls them. Somehow it works.

Attempt 3: Build a tree from callbacks

  • This was fun. Playdate calls you with events like:
    • “Starting object called ‘web-app’”
    • “Found key ‘servlet’, it’s an array”
    • “Array element 1 is an object”
    • … 200 more callbacks …
    • “Done with ‘web-app’”
  • Had to maintain a stack, track parent nodes, attach children at the right time.
  • Spent way too long debugging why web-app wasn’t found. Turns out Playdate wraps everything in _root. Classic.

Attempt 5: Why does it crash on device but work on simulator?

  • Simulator: Works perfectly, logs everything.
  • Device: Instant crash. No logs. Nothing.
  • The culprit? fmt.Sprintf(). Uses reflection. REFLECTION AGAIN.
  • Solution: Remove ALL fmt usage. Use string concatenation like cavemen.

The Secret Sauce

Added this little gem to make maps and slices work:

void tinygo_longjmp(void* buf) {
    pd->system->logToConsole("PANIC: tinygo_longjmp called - halting");
    while(1) {} // RIP
}

Hurray, it works! Thanks for reading!

Hello dear friends!

Here’s a quick look at what I’ve made over the past three weeks for the PdGo project…

v0.4.0 → 0.5.0 → 0.6.0 → 0.7.0 → v0.7.1

Three new official SDK examples ported to Go, plus implemented new sprite collision and tilemap APIs.

v0.5.0 - Tilemap Support

New API: Full Tilemap API implementation for drawing tile-based maps.

New Example: examples/tilemap - Official Playdate SDK Tilemap example
rewritten in Go. Demonstrates loading tilesets, creating tilemaps, and
rendering scrolling backgrounds.

v0.6.0 - Sprite Collisions & Query System

New API: Comprehensive sprite collision and spatial query system:

  • MoveWithCollisions - Move sprites with collision detection

  • CheckCollisions - Check collisions at a position

  • QuerySpritesAtPoint / QuerySpritesInRect - Spatial queries

  • QuerySpritesAlongLine / QuerySpriteInfoAlongLine - Raycasting with
    entry/exit points

  • OverlappingSprites / AllOverlappingSprites - Overlap detection

  • SpriteCollisionInfo and SpriteQueryInfo types with proper struct parsing

New Example: examples/sprite_collisions - Demonstrates bouncing sprites,

collision response, and raycasting with QuerySpriteInfoAlongLine.

v0.7.0 - Particles Example

New Example: examples/particles - Interactive particle system demonstrating:

  • Emitter configuration (emission rate, particle lifetime, acceleration)

  • Particle rendering and animation

  • User interaction (crank and buttons control particle properties)

v0.7.1 - Bug Fixes & Improvements

  • Fixed player movement in Sprite Collisions example

  • Added accessor functions for Sprite, Collision, and QueryInfo subsystems


Summary: 3 official SDK examples ported (Tilemap, Sprite Collisions,Particles), new Tilemap and Sprite Collision/Query APIs, and improved accessor
patterns. The roadmap progress continues toward full SDK example coverage.

Thanks for reading and for your interest!