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