Slide 1

Slide 1 text

Jakub Kozłowski | Scalar Conf | 22.03.2024 Foraging into embedded lands (The path to) writing Playdate games with Scala

Slide 2

Slide 2 text

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 😭

Slide 3

Slide 3 text

... • 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 💀

Slide 4

Slide 4 text

Part I: Prelude

Slide 5

Slide 5 text

What is the Playdate?

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

The Playdate • Small, handheld game console • 16MB RAM! • 168MHz 32-bit ARM CPU! • 400x240 1-bit screen! • No backlight! • O ff icial SDK!

Slide 8

Slide 8 text

Making games for the Playdate • O ff icially: C or Lua API • Uno ff icially: Rust, Swift, C++, Fortran, Nim, Typescript, D...

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Mixing Scala and the Playdate (Why this talk exists)

Slide 11

Slide 11 text

Why use Scala? • Typed functional programming • ADTs, pattern matching, lambdas, collections • GC • Because I like a challenge! (For game development)

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Build target choice Can be executed directly Needs to be loaded at runtime Baked into another library / application at linking (build) time

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Part II: The actual devil

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Step 3: run on the device for 32-bit support

Slide 21

Slide 21 text

Step 3: run on the device

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Step 3.1: Understanding what's wrong Build steps of a Scala Native program (vastly simpli f ied)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Step 3.2: f ixing the errors

Slide 26

Slide 26 text

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)

Slide 27

Slide 27 text

Atomics - ✅

Slide 28

Slide 28 text

Step 3.2: f ixing the errors • Delimited continuations • Just commented out

Slide 29

Slide 29 text

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!

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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!

Slide 35

Slide 35 text

Step 3.2: f ixing the errors

Slide 36

Slide 36 text

Step 3.2: f ixing the errors

Slide 37

Slide 37 text

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!

Slide 38

Slide 38 text

Part III: Finale

Slide 39

Slide 39 text

Scala gamedev on the Playdate

Slide 40

Slide 40 text

Demo time! I hope you've seen Michał's talk...

Slide 41

Slide 41 text

I also live in Wrocław And trams are only one of our problems

Slide 42

Slide 42 text

Demo

Slide 43

Slide 43 text

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"

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Scala dev on PD: implementation • All functions receive an immutable game context

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Summary

Slide 51

Slide 51 text

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)

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Links • Slides/contact/YouTube: linktr.ee/kubukoz • Playdate: https://play.date • Scala + PD: https:// devforum.play.date/t/scala- native-on-the-playdate/15814