add Nim Wasm template (#2645)

This commit is contained in:
Archar Gelod
2024-08-04 17:39:03 +08:00
committed by GitHub
parent 3c52c46c98
commit 55ee0b30f5
9 changed files with 565 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# Nim Starter Project Template
This is a Nim / TIC-80 starter template. To build wasm binary and import it into
the 'cart.tic' file, ensure you have a Nim compiler, Wasi-SDK and Tic-80 on your system and run:
```
nimble buildcart
```
Run `nimble tasks` command to see more build options.
+38
View File
@@ -0,0 +1,38 @@
# Package
version = "0.1.0"
author = "archargelod"
description = "tic80 wasm template for Nim language"
license = "MIT"
srcDir = "src"
# Dependencies
requires "nim >= 2.0.0"
# Tasks
import std/strformat
let pwd = getCurrentDir()
task wasmbuild, "Build wasm binary (debug)":
exec("nim c -o:cart.wasm src/cart")
task wasmrelease, "Build wasm binary (release)":
exec("nim c -d:release -o:cart.wasm src/cart")
task buildcart, "Build optimized wasm binary and import it to tic80 cart":
rmFile("cart.tic")
exec("nim c -d:release -o:cart.wasm src/cart")
exec(&"tic80 --cli --fs=\"{pwd}\" --cmd=\"load src/cart.tic & import binary cart.wasm & save cart.tic & exit\"")
task runcart, "Build optimized wasm binary and run it with tic80":
rmFile("cart.tic")
exec("nim c -d:release -o:cart.wasm src/cart")
exec(&"tic80 --skip --fs=\"{pwd}\" --cmd=\"load src/cart.tic & import binary cart.wasm & save cart.tic & run\"")
task debugcart, "Build wasm binary in debug mode and run it with tic80":
rmFile("cart.tic")
exec("nim c -o:cart.wasm src/cart")
exec(&"tic80 --skip --fs=\"{pwd}\" --cmd=\"load src/cart.tic & import binary cart.wasm & save cart.tic & run\"")
+52
View File
@@ -0,0 +1,52 @@
import std/os
let wasi = getEnv("WASI_SDK_PATH")
if wasi == "" and not defined(nimsuggest):
echo ""
echo "Error:"
echo "Download the WASI SDK (https://github.com/WebAssembly/wasi-sdk) and set the $WASI_SDK_PATH environment variable!"
echo ""
quit(-1)
switch("cpu", "wasm32")
switch("cc", "clang")
switch("threads", "off")
# ARC is much more embedded-friendly
switch("gc", "arc")
# Treats defects as errors, results in smaller binary size
switch("panics", "on")
# Use the common ANSI C target
switch("os", "any")
# Needed for os:any
switch("define", "posix")
# WASM has no signal handlers
switch("define", "noSignalHandler")
# The only entrypoints are start and update
switch("noMain")
# Use malloc instead of Nim's memory allocator, makes binary size much smaller
switch("define", "useMalloc")
switch("clang.exe", wasi / "bin" / "clang")
switch("clang.linkerexe", wasi / "bin" / "clang")
switch("passC", "--sysroot=" & (wasi / "share" / "wasi-sysroot"))
switch("passL", "-Wl,-zstack-size=8192,--no-entry,--import-memory -mexec-model=reactor -Wl,--initial-memory=262144,--max-memory=262144,--global-base=98304")
when not defined(release):
switch("assertions", "off")
switch("checks", "off")
switch("stackTrace", "off")
switch("lineTrace", "off")
switch("lineDir", "off")
switch("debugger", "native")
# This is needed to remove all unused Nim functions that will in turn import
# WASI stuff that is not available in WASM-4
switch("passL", "-Wl,--gc-sections")
else:
switch("opt", "size")
switch("passC", "-flto")
switch("passL", "-Wl,--strip-all,--gc-sections,--lto-O3")
Binary file not shown.
+84
View File
@@ -0,0 +1,84 @@
import std/[random]
import ../cart/tic80
let ToolbarHeight = 6
type
Bunny = object
x, y: float32
width: int32 = 26
height: int32 = 32
speedX, speedY: float32
sprite: int32
FPS = object
value, frames: int32
lastTime: float32
proc getValue(f: var FPS): int32 =
if time() - f.lastTime <= 1000:
inc f.frames
else:
f.value = f.frames
f.frames = 0
f.lastTime = time()
f.value
proc newBunny: Bunny =
Bunny(
x: rand(0 .. Width).float32,
y: rand(0 .. Height).float32,
width: 26,
height: 32,
speedX: rand(-100.0 .. 100.0) / 60,
speedY: rand(-100.0 .. 100.0) / 60,
sprite: 1,
)
proc draw(b: Bunny) =
spr(b.sprite, b.x.int32, b.y.int32, transColor=1, w=4, h=4)
proc update(b: var Bunny) =
b.x += b.speedX
b.y += b.speedY
if b.x.int32 + b.width > Width:
b.x = float32(Width - b.width)
b.speedX *= -1
if b.x < 0:
b.x = 0
b.speedX *= -1
if b.y.int32 + b.height > Height:
b.y = float32(Height - b.height)
b.speedY *= -1
if b.y < 0:
b.y = 0
b.speedY *= -1
randomize(tstamp().int64)
var fps: FPS
var bunnies: seq[Bunny]
proc TIC {.exportWasm.} =
cls(13)
if btn(P1_Up):
for _ in 1..5:
bunnies.add newBunny()
if btn(P1_Down):
for _ in 1..5:
if bunnies.len > 0:
bunnies.del bunnies.high
for bunny in bunnies.mitems:
bunny.update()
for bunny in bunnies.mitems:
bunny.draw()
rect(0, 0, Width, ToolbarHeight, 0)
print("Bunnies: " & $bunnies.len, 1, 0, 11, false, 1, false)
print("FPS: " & $fps.getValue(), Width div 2, 0, 11, false, 1, false)
+17
View File
@@ -0,0 +1,17 @@
import cart/tic80
var
t = 1
x = 96
y = 24
proc TIC {.exportWasm.} =
cls(Color13)
if btn(P1_Up): dec y
if btn(P1_Down): inc y
if btn(P1_Left): dec x
if btn(P1_Right): inc x
spr(1 + t mod 60 div 30 * 2, x, y, transColor=Color14, scale=3, w=2, h=2)
print("Hello from Nim!", 84, 84)
Binary file not shown.
+286
View File
@@ -0,0 +1,286 @@
const
TileSize* = 8
Width* = 240
Height* = 136
WidthTiles* = Width div TileSize
HeightTiles* = Height div TileSize
Bpp* = 4 ## bits per pixel
type
Key* = enum
Key_Null, Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G, Key_H, Key_I, Key_J,
Key_K, Key_L, Key_M, Key_N, Key_O, Key_P, Key_Q, Key_R, Key_S, Key_T, Key_U,
Key_V, Key_W, Key_X, Key_Y, Key_Z, Key_0, Key_1, Key_2, Key_3, Key_4, Key_5,
Key_6, Key_7, Key_8, Key_9, Key_Minus, Key_Equals, Key_Leftbracket,
Key_Rightbracket, Key_Backslash, Key_Semicolon, Key_Apostrophe, Key_Grave,
Key_Comma, Key_Period, Key_Slash, Key_Space, Key_Tab, Key_Return, Key_Backspace,
Key_Delete, Key_Insert, Key_Pageup, Key_Pagedown, Key_Home, Key_End, Key_Up,
Key_Down, Key_Left, Key_Right, Key_Capslock, Key_Ctrl, Key_Shift, Key_Alt
Button* = enum
P1_Up, P1_Down, P1_Left, P1_Right, P1_A, P1_B, P1_X, P1_Y,
P2_Up, P2_Down, P2_Left, P2_Right, P2_A, P2_B, P2_X, P2_Y,
P3_Up, P3_Down, P3_Left, P3_Right, P3_A, P3_B, P3_X, P3_Y,
P4_Up, P4_Down, P4_Left, P4_Right, P4_A, P4_B, P4_X, P4_Y
Color* = enum
Null = -1'i8, Color0, Color1, Color2, Color3, Color4, Color5, Color6, Color7,
Color8, Color9, Color10, Color11, Color12, Color13, Color14, Color15,
VRAM* {.bycopy.} = object
screen*: array[Width * Height * Bpp div 8, uint8]
palette*: array[48, uint8] ## 16 colors.
palletteMap*: array[8, uint8] ## 16 indices.
borderColorAndOvrTransparency*: uint8 ## Bank 0 is border color, bank 1 is OVR transparency.
screenOffsetX*: int8
screenOffsetY*: int8
mouseCursor*: int8
blitSegment*: uint8
reserved*: array[3, uint8]
MousePos* {.bycopy.} = object
x*: int16
y*: int16
scrollx*: int8
scrolly*: int8
left*: bool
middle*: bool
right*: bool
## ---------------------------
## Pointers
## ---------------------------
const
Framebuffer*: ptr Vram = cast[ptr Vram](0)
Tiles*: ptr uint8 = cast[ptr uint8](0x04000)
Sprites*: ptr uint8 = cast[ptr uint8](0x06000)
Map*: ptr uint8 = cast[ptr uint8](0x08000)
Gamepads*: ptr uint8 = cast[ptr uint8](0x0ff80)
Mouse*: ptr uint8 = cast[ptr uint8](0x0ff84)
Keyboard*: ptr uint8 = cast[ptr uint8](0x0ff88)
SfxState*: ptr uint8 = cast[ptr uint8](0x0ff8c)
SoundRegisters*: ptr uint8 = cast[ptr uint8](0x0ff9c)
Waveforms*: ptr uint8 = cast[ptr uint8](0x0ffe4)
Sfx*: ptr uint8 = cast[ptr uint8](0x100e4)
MusicPatterns*: ptr uint8 = cast[ptr uint8](0x11164)
MusicTracks*: ptr uint8 = cast[ptr uint8](0x13e64)
SoundState*: ptr uint8 = cast[ptr uint8](0x13ffc)
StereoVolume*: ptr uint8 = cast[ptr uint8](0x14000)
PersistentMemory*: ptr uint8 = cast[ptr uint8](0x14004)
SpriteFlags*: ptr uint8 = cast[ptr uint8](0x14404)
SystemFont*: ptr uint8 = cast[ptr uint8](0x14604)
WasmFreeRam*: ptr uint8 = cast[ptr uint8](0x18000)
## 160kb
## ---------------------------
## Constants
## ---------------------------
const
TilesSize*: uint32 = 0x2000
SpritesSize*: uint32 = 0x2000
MapSize*: uint32 = 32640
GamepadsSize*: uint32 = 4
MouseSize*: uint32 = 4
KeyboardSize*: uint32 = 4
SfxStateSize*: uint32 = 16
SoundRegistersSize*: uint32 = 72
WaveformsSize*: uint32 = 256
SfxSize*: uint32 = 4224
MusicPatternsSize*: uint32 = 11520
MusicTracksSize*: uint32 = 408
SoundStateSize*: uint32 = 4
StereoVolumeSize*: uint32 = 4
PersistentMemorySize*: uint32 = 1024
SpriteFlagsSize*: uint32 = 512
SystemFontSize*: uint32 = 2048
WasmFreeRamSize*: uint32 = 163840
## 160kb
{.push importc, codegenDecl: "__attribute__((import_name(\"$2\"))) $1 $2$3".}
## ---------------------------
## Drawing Procedures
## ---------------------------
proc circ*(x, y, radius: int32; color: Color)
## Draw a filled circle.
proc circb*(x, y, radius: int32; color: Color)
## Draw a circle border.
proc elli*(x, y, a, b: int32; color: Color)
## Draw a filled ellipse.
proc ellib*(x, y, a, b: int32; color: Color)
## Draw an ellipse border.
proc clip*(x, y, width, height: int32)
## Set the screen clipping region.
proc cls*(color: Color = Color0)
## Clear the screen.
proc font*(text: cstring; x, y: int32; trans_colors: ptr uint8; trans_count: int8,
char_width, char_height: int8; fixed: bool; scale: int8; alt: bool): int8
## Print a string using foreground sprite data as the font.
proc line*(x0, y0, x1, y1: cfloat; color: Color)
## Draw a straight line.
proc map*(x, y, w, h, sx, sy: int32; trans_colors: ptr uint8; colorCount: int8;
scale: int8; remap: int32)
## Draw a map region.
proc pix*(x, y: int32; color: Color): uint8 {.discardable.}
## Get or set the color of a single pixel.
proc print*(text: cstring; x, y: int32; color: Color = Color15; fixed = false;
scale: int32 = 1; alt = false): int32 {.discardable.}
## Print a string using the system font.
proc rect*(x, y, w, h: int32; color: Color)
## Draw a filled rectangle.
proc rectb*(x, y, w, h: int32; color: Color)
## Draw a rectangle border.
proc spr*(id, x, y: int32; trans_colors: ptr uint8; color_count: int8;
scale, flip, rotate, w, h: int32)
## Draw a sprite or composite sprite.
proc tri*(x1, y1, x2, y2, x3, y3: cfloat; color: Color)
## Draw a filled triangle.
proc trib*(x1, y1, x2, y2, x3, y3: cfloat; color: Color)
## Draw a triangle border.
proc ttri*(x1, y1, x2, y2, x3, y3, u1, v1, u2, v2, u3, v3: cfloat; texsrc: int32;
trans_colors: ptr uint8; color_count: int8; z1, z2, z3: cfloat; depth: bool)
## Draw a triangle filled with texture.
## ---------------------------
## Input Functions
## ---------------------------
proc btn*(button: Button): uint32
## Get gamepad button state in current frame.
proc btnp*(button: Button; hold: int32 = -1; period: int32 = -1): uint32
## Get gamepad button state according to previous frame.
proc key*(key: Key): bool
## Get keyboard button state in current frame.
proc keyp*(key: Key; hold: int32 = -1; period: int32 = -1): bool
## Get keyboard button state relative to previous frame.
proc mouse*(mouse_ptr_addy: ptr MousePos)
## Get XY and press state of mouse/touch.
## ---------------------------
## Sound Functions
## ---------------------------
proc music*(track, frame, row: int32; loop, sustain: bool; tempo, speed: int32)
## Play or stop playing music.
proc sfx*(sfx_id, note, octave, duration, channel, volume_left, volume_right,
speed: int32)
## Play or stop playing a given sound.
## ---------------------------
## Memory Functions
## ---------------------------
proc pmem*(address: int32; value: int64): uint32 {.discardable.}
## Access or update the persistent memory.
proc peek*(address: int32; bits: int8 = 8): int8
## Read a byte from an address in RAM.
proc peek1*(address: int32): int8
## Read a single bit from an address in RAM.
proc peek2*(address: int32): int8
## Read two bit value from an address in RAM.
proc peek4*(address: int32): int8
## Read a nibble value from an address.
proc poke*(address: int32; value: int8; bits: int8 = 8)
## Write a byte value to an address in RAM.
proc poke1*(address: int32; value: int8)
## Write a single bit to an address in RAM.
proc poke2*(address: int32; value: int8)
## Write a two bit value to an address in RAM.
proc poke4*(address: int32; value: int8)
## Write a nibble value to an address in RAM.
proc sync*(mask: int32 = 0; bank: int8 = 0; to_cart = false)
## Copy banks of RAM (sprites, map, etc) to and from the cartridge.
proc vbank*(bank: int8): int8
## Switch the 16kb of banked video RAM.
## ---------------------------
## Utility Functions
## ---------------------------
proc fget*(sprite_index: int32; flag: int8): bool
## Retrieve a sprite flag.
proc fset*(sprite_index: int32; flag: int8; value: bool): bool {.discardable.}
## Update a sprite flag.
proc mget*(x, y: int32): int32
## Retrieve a map tile at given coordinates.
proc mset*(x, y, value: int32)
## Update a map tile at given coordinates.
## ---------------------------
## System Functions
## ---------------------------
proc exit*()
## Interrupt program and return to console.
proc tstamp*(): uint32
## Returns the current Unix timestamp in seconds.
proc trace*(text: cstring; color: Color = Color15)
## Print a string to the Console.
{.pop.}
# suffix fixes "unreachable code executed" error
proc time2*(): cfloat {.importc,
codegenDecl: "__attribute__((import_name(\"time\"))) $1 $2$3".}
## Returns how many milliseconds have passed since game started.
import std/macros
macro exportWasm*(def: untyped): untyped =
result = def
result[^3] = nnkPragma.newTree(
ident("exportc"),
nnkExprColonExpr.newTree(
ident("codegenDecl"),
newStrLitNode("__attribute__((export_name(\"$2\"))) $1 $2$3")
)
)
+78
View File
@@ -0,0 +1,78 @@
import internal except btn, btnp, trace
export internal except btn, btnp, trace
proc font*(text: cstring, x, y: int32, transColor, char_width, char_height: int8,
fixed: bool, scale: int8, alt: bool): int8 {.discardable.} =
## Print a string using foreground sprite data as the font.
if transColor >= 0:
let trans_colors = transColor.uint8
internal.font(text, x, y, addr trans_colors, 1, char_width, char_height, fixed,
scale, alt)
else:
internal.font(text, x, y, nil, 0, char_width, char_height, fixed, scale, alt)
proc map*(x: int32 = 0, y: int32 = 0, w: int32 = 30, h: int32 = 17,
sx: int32 = 0, sy: int32 = 0, transColor: Color = Null, scale: int8 = 1,
remap: int32 = -1) =
## Draw a map region.
if transColor.int8 >= 0:
let trans_colors = transColor.uint8
internal.map(x, y, w, h, sx, sy, addr trans_colors, 1, scale, remap)
else:
internal.map(x, y, w, h, sx, sy, nil, 0, scale, remap)
proc spr*(id, x, y: int32, transColor: Color = Null, scale: int32 = 1,
flip: int32 = 0, rotate: int32 = 0, w: int32 = 1, h: int32 = 1) =
## Draw a sprite or composite sprite.
if transColor.int8 >= 0:
let trans_colors = transColor.uint8
internal.spr(id, x, y, addr trans_colors, 1, scale, flip, rotate, w, h)
else:
internal.spr(id, x, y, nil, 0, scale, flip, rotate, w, h)
proc ttri*(x1, y1, x2, y2, x3, y3, u1, v1, u2, v2, u3, v3: cfloat,
texsrc: int32 = 0, transColor: Color = Null,
z1: cfloat = 0, z2: cfloat = 0, z3: cfloat = 0, depth = false) =
## Draw a triangle filled with texture.
if transColor.int8 >= 0:
let trans_colors = transColor.uint8
internal.ttri(x1, y1, x2, y2, x3, y3, u1, v1, u2, v2, u3, v3, texsrc,
addr trans_colors, 1, z1, z2, z3, depth)
else:
internal.ttri(x1, y1, x2, y2, x3, y3, u1, v1, u2, v2, u3, v3, texsrc,
nil, 0, z1, z2, z3, depth)
proc btn*(index: Button): bool {.inline.} =
## Get gamepad button state in current frame.
internal.btn(index) > 0
proc btnp*(index: Button, hold: int32 = -1, period: int32 = -1): bool {.inline.} =
## Get gamepad button state according to previous frame.
internal.btnp(index, hold, period) > 0
proc mouse*(): MousePos {.inline.} = internal.mouse(addr result)
## Get XY and press state of mouse/touch.
proc pmemset*(address: int32; value: int64) = pmem(address, value)
## Update the persistent memory.
proc pmemget*(address: int32): uint32 = pmem(address, -1)
## Access the persistent memory.
proc pixset*(x, y: int32; color: Color) = pix(x, y, color)
## Set the color of a single pixel.
proc pixget*(x, y: int32): uint8 = pix(x, y, Null)
## Get the color of a single pixel.
proc trace*(items: varargs[cstring, cstring]) =
## Print a string to the Console.
for item in items: internal.trace(item)
proc print*(text: string; x, y: int32; color: Color = Color15; fixed = false;
scale: int32 = 1; alt = false): int32 {.discardable.} =
## Print a string using the system font.
print(cstring(text), x, y, color, fixed, scale, alt)
proc NimMain() {.importc.}
proc BOOT() {.exportWasm.} =
NimMain()