Playdate SDK with TypeScript

:wave:

I’ve been exploring using TypeScript to build my games (I work on the language, so it’s good to dogfood) - it doesn’t negate needing to learn Lua, but it does carry the torch of JS ruining touching everything. It uses TypeScriptToLua, my VS Code extension and I’ve been building out a definition file for the Playdate SDK (in the zip).

To test it all out, I’ve built a trivial-ish game of snake, which is enough to prove that the whole pipeline works.

If you want to give it a run - here’s a zip of the working example (to actually develop in it, you will need to run yarn install - I included the output Lua files in there in case folks were interested in seeing the ts2lua results. )

10 Likes

I can’t download that zip, permissions issue.

Heh, maybe abusing discord URLs isn’t the best pattern, here’s a copy on my S3
https://ortastuff.s3.us-east-1.amazonaws.com/playdate/snake-in-typescript.zip

1 Like

Joined a minute ago, first post that caught my interested, and boom – of course you find @orta here :slight_smile: Nice work!

1 Like

This is very exciting! I am personally a statically-typed sort of guy, so the flexibility of Lua sometimes gives me the creeps. Should there be much of a performance hit by coding in TypeScript, then cross-compiling* to Lua?

Would it be possible to cross-compile* TypeScript to C?

'* I’m not sure if “cross-compile” is the proper way to describe what you’re doing.

I believe the term used is … transpiling? Transforming one language to another. Anyway, very cool @orta!

There are TypeScript to C, um, transpilers: https://github.com/sebbekarlsson/tscc

Yeah, transpiling is generally the “move sideways” word to describe these.

IMO: perf will not be as good as well written Lua, but probably good enough and you can still write in Lua as well as TypeScript if you have code which has to be very fast.

The perf trade-offs come from two places:

  • The language impedance mis-match. Things which are easy and common in JavaScript are not always in cheap Lua. TS2Lua offers polyfills for most common cases, which I use in a bunch of places. Things like array pushing, concating or removing items don’t directly have Lua 1-liners. This is like JS bread and butter.

  • The TS2Lua translated Lua looks like it’s optimized for large codebases and prefers globals over locals when it can. Which is regularly brought up as a perf hit in the Playdate chat. This could maybe be worked around at the ts2lua transpiler level as this is likely a design choice they made.

1 Like

Re: transpiling to C - folks are exploring. IMO the most relevant one comes from the makecode team called Static TypeScript, which is built for making games on micro-controllers. It’s the TS language without JS quirks, so kinda dreamy TBH - I wrote my last experimental game in it and they did express an interest to me a few months ago about wanting to look at the Playdate, so I can try help you all connect if you’re interested.

2 Likes

We would definitely be interested in talking to them. We are in a bit of a crunch right now, so I don’t know how much attention we could devote to them at this moment… but medium- to long-term, the idea of a high-level language that might run with something closer to the performance of C would be really interesting to us! If they are interested, have them get in touch with me at greg@panic.com. Thank you!

3 Likes

Interestingly, I found this project that compiles TS via LLVM. Having some trouble compiling it for Apple Silicon but that could be interesting as a native LLVM vector.

Update: I was able to compile it but I had to tweak a couple of lines in a C++ file. :man_shrugging: Now I'm running into some LLVM issues (I think).

Update 2: This is bonkers and nothing is working. I think @orta's idea to transpile the TS to Lua is probably a better ROI.

1 Like

I spent a week or so digging into different TS -> C compilers, and went down the same rabbit hole you did @_a2.

TypeScriptToLua worked great, and I've been putzing with writing out the full API in type declarations. There are some useful compiler annotations that I've been taking advantage of, as well as custom types like LuaMultiReturn. Operator map types also make it possible to compile the custom operations from lua, like addition between vectors.

So far I have animators, geometry, and graphics ported, but not fully tested.

anim

Types have been great so far, like having animator:currentValue() be type based on which new was used to construct it. I'll see about pushing code up sometime soon!

Edit 1: Pathfinding is now working. Some off-by-1 errors in the port, which is kind of the name of the game for lua -> TS.
pathfinding

Edit 2: This is gridview.lua from the examples, which is grid view, nineslice, and timers. I need to make timers a little more exactly typed, they're tricky because they support a constructor with any number of arguments, which become parameters to the timer callbacks. Just some generics I need to sort out.
gridview

Edit 3: I was able to get multi-file TypeScript projects to compile using TypeScriptToLua's bundling. All Lua is jammed into a single file, and require is mocked to load code from within that bundle (like how JS bundlers work).
The next, big thing to bite off is going to be implementing some sort of OOP. TSTL exposes plugins, which let you create arbitrary AST nodes. TypeScript class definitions will need to be ported to the custom Playdate API's class stuff, i.e.

class Foo extends Bar {
    constructor() {
        super();
    }
    method() {
        this.x = 2;
    }
}

becomes

class('Foo').extends(Bar)
function Bar::init()
  Bar.super.init(self)
end
function Bar::method()
  self.x = 2
end

Anyway, here's the Level 1-1 example from the SDK, mostly ported to TypeScript. Instead of doing the OOP stuff, I just have functions that spit out an object that mocks the properties a sprite would have, like:

const Player = () => {
  // ...
  const underlyingSprite = playdate.graphics.sprite.new();
  return {
    moveWithCollisions: (p: playdate.Point) => {
        return underlyingSprite.moveWithCollisions(p);
    }
}

11

Edit 4: I have the first pass of OOP transformations working! The following .ts:

require('CoreLibs/sprites');
require('CoreLibs/object');

const sprite = playdate.graphics.sprite;

class Ball extends playdate.graphics.sprite {
    constructor() {
        super();
        this.setImage(playdate.graphics.image.new('img/brick'));
    }

    draw(x: number, y: number, width: number, height: number): void {
        playdate.graphics.drawCircleAtPoint(x, y, 100);
    }
}

const x = new Ball();
x.add();
x.moveTo(100, 100);

playdate.update = () => {
    sprite.update();
};

export {};

compiles to

import("CoreLibs/sprites")
import("CoreLibs/object")
local sprite = playdate.graphics.sprite
class("Ball").extends(playdate.graphics.sprite)
Ball.init = function(self)
    Ball.super.init(self)
    self:setImage(playdate.graphics.image.new("img/brick"))
end
function Ball.draw(self, x, y, width, height)
    playdate.graphics.drawCircleAtPoint(x, y, 100)
end
local x = Ball()
x:add()
x:moveTo(100, 100)
playdate.update = function()
    sprite:update()
end
return ____exports

which in turn draws a little brick on the screen.

My guess is that my transformations are pretty fragile. I know I don't support class properties yet, and I'm not yet sure how I'll approach things like statics.

6 Likes

How did you get this to work? I wrote my own playdate.ts plugin that I was working off of. I didn't know if anyone else would be interested in this. I'm not sure if Playdate's "flavor" of Lua works with the return ____exports thing so I tried to skirt around that. DM me if you want to collaborate on this somehow.

Hi Alex!

Playdate's Lua works with TSTL's ____exports business! In the bundle TSTL makes, it mocks the require function to load the exports from the table it builds up. I'm using @orta 's TSTL plugin, which transforms require() calls to import() calls, then TSTL turns TypeScript's import statements into require.

I can share my code, both for my TSTL and my type definitions. I have the entire API in a playdate.d.ts, although I have found some bugs while porting example projects so it's certainly not perfect.

I'm away from that computer this weekend, but I'll share some .zips on here on Monday, most likely! Eventually I'd like to get the types and the plugin into npm, but it's still just something I'm mucking around with when I have a spare moment. I'll try to get the types and plugins on GitHub soon so anyone can contribute.

Okeydoke, I lumped my playdate.d.ts and TSTL plugin into a single directory and just zipped it all up. You should be able to npm i && npm run build and have a working pdx.

Let me know if it doesn't work or if you have questions or suggestions. I'll try to figure out a plan for getting this up on GitHub or something, like I mentioned.

PlaydateTSExample.zip (26.4 KB)

Do you guys have any updates on this? Like a Github repository?

The types declarations from Andy seems pretty complete.