Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Foraging into embedded lands - (the path to) writing Playdate games in Scala

Foraging into embedded lands - (the path to) writing Playdate games in Scala

Let's write a game in Scala Native, for Playdate (https://play.date)!

It's a tiny console with very limited hardware and a 1-bit black&white screen. Having a well-documented C API, it's a perfect candidate for playing with using Scala Native! What could go wrong...

Turns out, it's quite challenging, especially for someone mostly working in the safe haven of the JVM.

In this talk, I'll share with you:
- What I've learned about Scala Native as part of this journey
- Problems I've stumbled upon on my way to run SN on an unsupported platform
- A demo on the actual device.

Jakub Kozłowski

March 22, 2024
Tweet

More Decks by Jakub Kozłowski

Other Decks in Programming

Transcript

  1. Jakub Kozłowski | Scalar Conf | 22.03.2024 Foraging into embedded

    lands (The path to) writing Playdate games with Scala
  2. Who am I? • Technical team lead @ • OSS

    contributor, smithy4s maintainer • Working on Scala/Smithy tooling on a daily basis • Spending most of my free time dealing with this demon This was in June, she's so much bigger now 😭
  3. ... • I'm also working on a game about my

    pup's eating habits • ...but it's in C so I won't talk about that today 💀
  4. The Playdate • Small, handheld game console • 16MB RAM!

    • 168MHz 32-bit ARM CPU! • 400x240 1-bit screen! • No backlight! • O ff icial SDK!
  5. Making games for the Playdate • O ff icially: C

    or Lua API • Uno ff icially: Rust, Swift, C++, Fortran, Nim, Typescript, D...
  6. Why use Scala? • Typed functional programming • ADTs, pattern

    matching, lambdas, collections • GC • Because I like a challenge! (For game development)
  7. Now it's time to analyze our options, right? • Consider

    GraalVM Native Image, or other Embedded Java implementations? • Nah, I just want an excuse to use Scala Native.
  8. Build target choice Can be executed directly Needs to be

    loaded at runtime Baked into another library / application at linking (build) time
  9. Which one shouuld we use? • PD games are built

    as dynamic libraries • Dependencies on other dylibs not allowed • To call Scala, you need to call ScalaNativeInit() f irst • Thus, we'll need a statically linked SN library
  10. The plan 1. Generate API/SDK bindings 2. Run a Scala

    game in the simulator 3. Run a Scala game on the device, no GC 4. Add GC 5. Move on to the fun part
  11. Step 1: bindings • Using sn-bindgen • 🙂 Mostly a

    breeze • 😐 A couple unnamed structs/unions, simple f ind/ replace to resolve that • 😕 no continuous updates because of the manual step
  12. Step 2: run in simulator • Pladate SDK includes a

    simulator (not an emulator) • Runs natively on your platform • On Mac, this is built purely with Clang! • Device builds use arm- speci f ic GCC • Built and ran Scala in one day
  13. Step 3.1: Understanding what's wrong • Main sources of errors

    • Atomics • Delimited continuations support • Stack traces • POSIX APIs • Networking • cwd/pwd/uid • threads • C++ std APIs • GC
  14. Step 3.2: f ixing the errors • Atomics • single-threaded

    environment, shouldn't be necessary (replace with normal swaps and assume it's successful f irst time)
  15. Step 3.2: f ixing the errors • Stack traces •

    I decided to ignore them for my own sanity • Stub out • As a result, any stack traces I see will be useless!
  16. Step 3.2: f ixing the errors • POSIX APIs •

    Networking: not used, remove/stub out x20
  17. Step 3.2: f ixing the errors • POSIX APIs •

    Cwd/pwd/uid: used when System is initialized. Hardcoded arbitrary values
  18. Step 3.2: f ixing the errors • POSIX APIs: threads!

    • Technically, we're single threaded so shouldn't need it... • But MainThread needs to be initialized on startup, e.g. for ThreadLocals which are used even by decoding bytes to String
  19. Step 3.2: f ixing the errors • POSIX APIs: threads!

    • MainThread: hardcode identi f ier (it's not gonna be used anyway) • Platform-speci f ic Thread implementation (WindowsThread/ PosixThread), methods can be stubbed as we're not using them
  20. Step 3.2: f ixing the errors • C++ APIs •

    SN uses some C++ types (exceptions) • PD SDK doesn't support C++ by default (unde f ined symbols) • For speed, started from playdate- cpp and backtracked to "pure" C with stubs for C++ symbols courtesy of the project • It worked pretty well!
  21. Step 4: GC (long story short) • GC was being

    a problem • Wojciech Mazur agreed to pair on this, he quickly found the culprit(s) in my changes. • Problem: I had replaced the missing mmap with malloc, meaning the GC couldn't assume allocated memory was contiguous. Solution: allocate all memory in one go. • He also made f ixes to 32-bit GC in the upstream! That f ixed pretty much everything. • Massive thanks, Wojtek!
  22. Scala dev on PD: basic idea • build a datatype

    that describes the screen state / events in "game code" • Interpret to side e ff ects in "library code"
  23. Scala dev on PD: implementation • Split out state updates

    from rendering • Heavily inspired by Indigo / Tyrian / the elm architecture • No abstraction on top, to allow quick experimentation
  24. Scala dev on PD: init • Init returns a Resource,

    similar to Cats E ff ect but synchronous • Composes monadically,gets allocated once at startup and cleaned up at shutdown • Haven't needed dynamic resource allocation yet, but I'm looking forward to exploring that space functionally! • Assets are stored in game state, as pointers. Could easily be hidden behind opaque types
  25. Scala dev on PD: update • Update receives the previous

    state and the game context • State directly stores events, so update has to clear them by itself • Events are added as part of the update sub-steps
  26. Scala dev on PD: render • Render receives the state

    and returns a Render • Just a data structure • (for the most part) • Events such as playing sounds have to be turned into a Render explicitly by render code
  27. Roadmap • Upstreaming patches to Scala Native • One day,

    hoping to run without a fork • Cleaning up the build process to remove playdate-cpp • Packaging everything in a library and optional sbt plugin • Expanding and publishing the game
  28. I was (still am) very new to this • I

    only had surface-level experience with Scala Native before • Had to learn a lot of new tools and their f lags (clang, ld, objdump, readelf) • Wouldn't have made it if not for the help of many amazing people (SN and Panic side)
  29. My takeaways • Sometimes there's only one way to f

    ind out if you can: try • Don't be afraid to ask for help • You don't have to be an expert in XYZ to make use of XYZ in a unique and novel way
  30. Links • Slides/contact/YouTube: linktr.ee/kubukoz • Playdate: https://play.date • Scala +

    PD: https:// devforum.play.date/t/scala- native-on-the-playdate/15814