Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved D template #1891

Merged
merged 7 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/api/wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ M3Result linkTicAPI(IM3Module module)
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "font", "i(*iiiiiii)", &wasmtic_font)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "key", "i(i)", &wasmtic_key)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "keyp", "i(iii)", &wasmtic_keyp)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "line", "v(iiiii)", &wasmtic_line)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "line", "v(ffffi)", &wasmtic_line)));
// TODO: needs a lot of help for all the optional arguments
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "map", "v(iiiiiiiiii)", &wasmtic_map)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "memcpy", "v(iii)", &wasmtic_memcpy)));
Expand Down Expand Up @@ -994,9 +994,9 @@ M3Result linkTicAPI(IM3Module module)
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "time", "f()", &wasmtic_time)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "tstamp", "i()", &wasmtic_tstamp)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "trace", "v(*i)", &wasmtic_trace)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "tri", "v(iiiiiii)", &wasmtic_tri)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "trib", "v(iiiiiii)", &wasmtic_trib)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "ttri", "v(iiiiiiiiiiiiiiiiiii)", &wasmtic_ttri)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "tri", "v(ffffffi)", &wasmtic_tri)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "trib", "v(ffffffi)", &wasmtic_trib)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "ttri", "v(ffffffffffffiiifffi)", &wasmtic_ttri)));
_ (SuppressLookupFailure (m3_LinkRawFunction (module, "env", "vbank", "i(i)", &wasmtic_vbank)));

_catch:
Expand Down
10 changes: 10 additions & 0 deletions templates/d/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
DUB_FLAGS = --quiet --build release --compiler ldc2 --arch wasm32-unknown-unknown-wasm
ifneq ($(origin WASI_SDK_PATH), undefined)
override DUB_FLAGS += --config wasi
endif

build:
dub build ${DUB_FLAGS}

clean:
rm -rf cart.wasm .dub
59 changes: 49 additions & 10 deletions templates/d/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,69 @@
# D Starter Project Template

This is a D TIC-80 starter template. To build the D source files:
## Pre-requisites

- [LDC](https://wiki.dlang.org/LDC)
- [WASI libc](https://github.com/WebAssembly/wasi-libc)

### WASI libc

WASI libc's [README](https://github.com/WebAssembly/wasi-libc#usage) states
that "the easiest way to get started with this is to use wask-sdk, which
includes a build of WASI libc in its sysroot."

Just [building WASI libc from source](https://github.com/WebAssembly/wasi-libc#building-from-source)
works too, for the purpose of programming TIC-80 games in D. Let's say you
install into ```$HOME/wasi-sdk/share/wasi-sysroot```, which then look like this:

```
% ./build.sh
% ls -l
total 12
drwxr-xr-x 10 pierce pierce 4096 Apr 24 16:19 include/
drwxr-xr-x 3 pierce pierce 4096 Apr 24 16:19 lib/
drwxr-xr-x 3 pierce pierce 4096 Apr 24 16:19 share/
```

To import the resulting WASM file into a cartridge:
## Files in this template

- ```buildcart.sh``` - convenience script to build and run the game cartridge
- ```buildwasm.sh``` - convenience script to build and run the Wasm program
- ```dub.json``` - D's dub package description file
- ```Makefile``` - convenience Makefile that invokes ```dub```
- ```wasmdemo.wasmp``` - TIC-80 Wasm 'script' file. Note the embedded game assets data at the end of the file.

## Building your game

Define the environment variable WASI_SDK_PATH; e.g., if you installed WASI
libc into ```$HOME/wasi-sdk/share/wasi-sysroot```, then ```export WASI_SDK_PATH=$HOME/wasi-sdk```.

Edit ```src/main.d``` to implement your game. You are of course free to
organize your code in more than one D source file.

If you create sprites, map, music, etc., for your game, remember to
replace the game asset data at the end of ```wasmdemo.wasmp``` with
your creations.

To build the Wasm file, execute ```make```. This generates ```cart.wasm```
in the current directory. To run:

```
% tic80 --fs .
tic80 prompt> load wasmdemo.wasmp
tic80 prompt> import binary cart.wasm
tic80 prompt> save game.tic
tic80 prompt> exit
% tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & run & exit'
```

Or, on the command line:
The script ```buildwasm.sh``` contains above steps as a convenience.

To build a TIC-80 cartridge, first build the Wasm file, then build the
cartridge file:

```
% tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & save game.tic & exit'
```

Now, run ```game.tic```:
You can then run your cartridge as follows:

```
% tic80 --fs . --cmd 'load game.tic & run & exit'
```

The script ```buildcart.sh``` does the above steps as a convenience.

2 changes: 0 additions & 2 deletions templates/d/build.sh

This file was deleted.

6 changes: 6 additions & 0 deletions templates/d/buildcart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
rm -f game.tic
make clean
make
tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & save game.tic & exit'
tic80 --fs . --cmd 'load game.tic & run & exit'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to invoke tic80 twice vs doing it all in a single session?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intention is to demonstrate the two separate actions -- creating a .tic file from necessary artifacts, and running the .tic directly without reference to said artifacts -- for the programmer who is new to this console.

4 changes: 4 additions & 0 deletions templates/d/buildwasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
make clean
make
tic80 --fs . --cmd 'load wasmdemo.wasmp & import binary cart.wasm & run & exit'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just me, but nothing about build to me implies "run" also... I think perhaps we need build_and_run or something...? Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine calling it build_and_run. As is typical, this started out with the make commands, and then I added the tic80 command line too to run. As the README states, these are just convenience scripts.

38 changes: 38 additions & 0 deletions templates/d/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "cart",
"targetType": "executable",
"configurations": [
{
"name": "raw",
"dflags": [
"-betterC"
],
"lflags": [
"--strip-all",
"--allow-undefined",
"--stack-first",
"--no-entry"
Copy link
Collaborator

@joshgoebel joshgoebel Apr 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to pull over:

       "--initial-memory=65536",
       "--max-memory=65536",
       "-zstack-size=14752"

But with TIC-80 specific values, which I think would mean 256kb memory and 96kb "reserved"... which I THINK means stack-size should be 96*1024 + 8 * 1024 (assuming we want an 8kb actual stack)... does that sound right? With the stack growing downwards from 96kb+8kb, sicne we're using --stack-first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me run some tests with the numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the numbers for TIC-80 would be, for 8k actual stack:

       "--initial-memory=262144",
       "--max-memory=262144",
       "-zstack-size=106496"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we ever get this changed?

]
},
{
"name": "wasi",
"dflags": [
"-betterC",
"-Xcc",
"$WASI_SDK_PATH/share/wasi-sysroot"
],
"lflags": [
"--strip-all",
"--allow-undefined",
"--stack-first",
"--no-entry",
"$WASI_SDK_PATH/share/wasi-sysroot/lib/wasm32-wasi/libc.a"
]
}
],
"toolchainRequirements": {
"dmd": "no",
"gdc": "no",
"ldc": ">=1.11.0"
}
}
25 changes: 24 additions & 1 deletion templates/d/src/main.d
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import std.algorithm: min, max;
import tic80;

extern(C):

// From WASI libc:
int snprintf(scope char* s, size_t n, scope const char* format, scope const ...);

int t, x, y;
const char* m = "HELLO WORLD FROM D!";
int r = 0;
MouseData md;

void BOOT() {
t = 1;
Expand All @@ -12,13 +18,30 @@ void BOOT() {
}

void TIC() {
cls(13);

// The standard demo.
if (btn(0) > 0) { y--; }
if (btn(1) > 0) { y++; }
if (btn(2) > 0) { x--; }
if (btn(3) > 0) { x++; }

cls(13);
spr(1+t%60/30*2, x, y, null, 0, 3, 0, 0, 2, 2);
print(m, 60, 84, 15, 1, 1, 0);
t++;

// Mouse example demonstrating use of libc function.
mouse(&md);
if (md.left) { r = r + 2; }
r--;
r = max(0, min(32, r));
line(md.x, 0, md.x, 136, 11);
line(0, md.y, 240, md.y, 11);
circ(md.x, md.y, r, 11);

const BUFSIZ = 10;
char[BUFSIZ] buf;
char* bufptr = cast(char*)buf;
snprintf(bufptr, BUFSIZ, "(%03d,%03d)", md.x, md.y);
print(bufptr, 3, 3, 15, 0, 1, 1);
}
60 changes: 51 additions & 9 deletions templates/d/src/tic80.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,55 @@ extern(C):

struct MouseData {
short x; short y;
short scrollx; short scrolly;
byte scrollx; byte scrolly;
bool left; bool middle; bool right;
}

const int WIDTH = 240;
const int HEIGHT = 136;

// These are pointers.
const FRAMEBUFFER_PTR = cast(ubyte*)0;
const TILES_PTR = cast(ubyte*)0x4000;
const SPRITES_PTR = cast(ubyte*)0x6000;
const MAP_PTR = cast(ubyte*)0x8000;
const GAMEPADS_PTR = cast(ubyte*)0xFF80;
const MOUSE_PTR = cast(ubyte*)0xFF84;
const KEYBOARD_PTR = cast(ubyte*)0xFF88;
const SFX_STATE_PTR = cast(ubyte*)0xFF8C;
const SOUND_REGISTERS_PTR = cast(ubyte*)0xFF9C;
const WAVEFORMS_PTR = cast(ubyte*)0xFFE4;
const SFX_PTR = cast(ubyte*)0x100E4;
const MUSIC_PATTERNS_PTR = cast(ubyte*)0x11164;
const MUSIC_TRACKS_PTR = cast(ubyte*)0x13E64;
const SOUND_STATE_PTR = cast(ubyte*)0x13FFC;
const STEREO_VOLUME_PTR = cast(ubyte*)0x14000;
const PERSISTENT_MEMORY_PTR = cast(ubyte*)0x14004;
const SPRITE_FLAGS_PTR = cast(ubyte*)0x14404;
const SYSTEM_FONT_PTR = cast(ubyte*)0x14604;
const WASM_FREE_RAM_PTR = cast(ubyte*)0x18000;
Copy link
Collaborator

@joshgoebel joshgoebel Apr 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really think we should provide this out of the box? Does D not allocate globals and such things starting here automatically? (like C)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose you are referring to WASM_FREE_RAM_PTR? Since TIC-80 allows peeking and poking anywhere, I put this in as well. But yes, generally the game programmer will leave to D to manage memory.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know. I'm just not sure we want to encourage this. Someone who knows what they are doing can add that line to their own source easily enough without us including it in the core examples. And if they do not know what they are doing they probably shouldn't be using WASM_FREE_RAM_PTR in the first place...

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it out then.


// These are bounded arrays.
ubyte[] FRAMEBUFFER() { return (cast(ubyte*)0)[0..16319]; } // VRAM bank 0 screen area
ubyte[] TILES() { return (cast(ubyte*)0x4000)[0..8191]; }
ubyte[] SPRITES() { return (cast(ubyte*)0x4000)[0..8191]; }
ubyte[] MAP() { return (cast(ubyte*)0x8000)[0..32639]; }
ubyte[] GAMEPADS() { return (cast(ubyte*)0xFF80)[0..3]; }
ubyte[] MOUSE() { return (cast(ubyte*)0xFF84)[0..3]; }
ubyte[] KEYBOARD() { return (cast(ubyte*)0xFF88)[0..3]; }
ubyte[] SFX_STATE() { return (cast(ubyte*)0xFF8C)[0..15]; }
ubyte[] SOUND_REGISTERS() { return (cast(ubyte*)0xFF9C)[0..71]; }
ubyte[] WAVEFORMS() { return (cast(ubyte*)0xFFE4)[0..255]; }
ubyte[] SFX() { return (cast(ubyte*)0x100E4)[0..4223]; }
ubyte[] MUSIC_PATTERNS() { return (cast(ubyte*)0x11164)[0..11519]; }
ubyte[] MUSIC_TRACKS() { return (cast(ubyte*)0x13E64)[0..407]; }
ubyte[] SOUND_STATE() { return (cast(ubyte*)0x13FFC)[0..3]; }
ubyte[] STEREO_VOLUME() { return (cast(ubyte*)0x14000)[0..3]; }
ubyte[] PERSISTENT_MEMORY() { return (cast(ubyte*)0x14004)[0..1023]; }
ubyte[] SPRITE_FLAGS() { return (cast(ubyte*)0x14404)[0..511]; }
ubyte[] SYSTEM_FONT() { return (cast(ubyte*)0x14604)[0..2047]; }
ubyte[] WASM_FREE_RAM() { return (cast(ubyte*)0x18000)[0..163839]; } // 160kb

int btn(int id);
bool btnp(int id, int hold, int period);
void circ(int x, int y, int radius, int color);
Expand All @@ -25,7 +67,7 @@ int font(char* text, int x, int y, ubyte transcolors, int colorcount, int width,
bool fset(int id, ubyte flag, bool value);
bool key(int keycode);
bool keyp(int keycode, int hold, int period);
void line(int x0, int y0, int x1, int y1, int color);
void line(float x0, float y0, float x1, float y1, byte color);
void map(int x, int y, int w, int h, int sx, int sy, ubyte transcolors, int colorcount, int scale, int remap);
void memcpy(uint copyto, uint copyfrom, uint length);
void memset(uint addr, ubyte value, uint length);
Expand All @@ -34,27 +76,27 @@ void mouse(MouseData* data);
void mset(int x, int y, bool value);
void music(int track, int frame, int row, bool loop, bool sustain, int tempo, int speed);
ubyte peek(int addr, int bits);
int print(const char* txt, int x, int y, int color, int fixed, int scale, int alt);
ubyte peek4(uint addr4);
ubyte peek2(uint addr2);
ubyte peek1(uint bitaddr);
void pix(int x, int y, int color);
uint pmem(uint index, uint value);
void poke(uint addr, ubyte value, int bits);
void poke4(uint addr4, ubyte value);
void poke2(uint addr2, ubyte value);
void poke1(uint bitaddr, ubyte value);
void poke(int addr, byte value, byte bits);
void poke4(int addr4, byte value);
void poke2(int addr2, byte value);
void poke1(int bitaddr, byte value);
int print(const char* txt, int x, int y, int color, int fixed, int scale, int alt);
void rect(int x, int y, int w, int h, int color);
void rectb(int x, int y, int w, int h, int color);
void reset();
void sfx(int id, int note, int octave, int duration, int channel, int volumeLeft, int volumeRight, int speed);
void spr(int id, int x, int y, uint* transcolors, uint colorcount, int scale, int flip, int rotate, int w, int h);
void sync(int mask, int bank, bool tocart);
void trace(const char* txt, int color);
void textri(float x1, float y1, float x2, float y2, float x3, float y3, float u1, float v1, float u2, float v2, float u3, float v3, int texsrc, ubyte transcolors, int colorcount, float z1, float z2, float z3, bool persp);
void ttri(float x1, float y1, float x2, float y2, float x3, float y3, float u1, float v1, float u2, float v2, float u3, float v3, int texsrc, ubyte transcolors, int colorcount, float z1, float z2, float z3, bool persp);
void tri(float x1, float y1, float x2, float y2, float x3, float y3, int color);
void trib(float x1, float y1, float x2, float y2, float x3, float y3, int color);
int time();
float time();
int tstamp();
int vbank(int bank);