Skip to content

Commit dd7f6e9

Browse files
committed
TinyGO WASM build
1 parent c7cbd34 commit dd7f6e9

File tree

7 files changed

+143
-79
lines changed

7 files changed

+143
-79
lines changed

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ build-x: ## cross compile for linux_amd64 and win_amd64 targets (requires docke
3333
build-wasm: ## build wasm
3434
@echo "--------- running: $@ ---------"
3535
cp "$(shell go env GOROOT)/misc/wasm/wasm_exec.js" ./web
36-
GOOS=js GOARCH=wasm go build -o=web/dendy.wasm ./cmd/dendy-wasm
36+
GOOS=js GOARCH=wasm go build -pgo=default.pgo -o=web/dendy.wasm ./cmd/dendy-wasm
37+
38+
.PHONY: build-wasm-tinygo
39+
build-wasm-tinygo: ## build wasm
40+
@echo "--------- running: $@ ---------"
41+
cp "$(shell tinygo env TINYGOROOT)/targets/wasm_exec.js" ./web
42+
tinygo build -no-debug -gc=conservative -target=wasm -opt=2 -o=web/dendy.wasm ./cmd/dendy-wasm
3743

3844
PHONY: test
3945
test: ## run tests

cmd/dendy-wasm/main.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,58 @@ package main
22

33
import (
44
_ "embed"
5+
"fmt"
56
"log"
7+
"runtime"
68
"syscall/js"
9+
"time"
710
"unsafe"
811

912
"github.com/maxpoletaev/dendy/ines"
1013
"github.com/maxpoletaev/dendy/input"
1114
"github.com/maxpoletaev/dendy/system"
1215
)
1316

17+
const (
18+
debugFrameTime = false
19+
)
20+
1421
//go:embed nestest.nes
15-
var initialRomData []byte
22+
var bootROM []byte
1623

17-
func createSystem(joy *input.Joystick, romData []byte) *system.System {
24+
func createSystem(joy *input.Joystick, romData []byte) (*system.System, error) {
1825
rom, err := ines.NewFromBuffer(romData)
1926
if err != nil {
20-
log.Fatalf("failed to load ROM: %v", err)
21-
return nil
27+
return nil, fmt.Errorf("failed to load ROM: %v", err)
2228
}
2329

2430
cart, err := ines.NewCartridge(rom)
2531
if err != nil {
26-
log.Fatalf("failed to create cartridge: %v", err)
27-
return nil
32+
return nil, fmt.Errorf("failed to create cartridge: %v", err)
2833
}
2934

3035
zapper := input.NewZapper()
3136
nes := system.New(cart, joy, zapper)
32-
33-
return nes
37+
return nes, nil
3438
}
3539

3640
func main() {
37-
joy1 := input.NewJoystick()
38-
nes := createSystem(joy1, initialRomData)
41+
log.SetFlags(0)
42+
joystick := input.NewJoystick()
43+
44+
nes, err := createSystem(joystick, bootROM)
45+
if err != nil {
46+
log.Fatalf("[ERROR] failed to initialize: %v", err)
47+
}
48+
49+
var (
50+
mem runtime.MemStats
51+
frameTimeSum time.Duration
52+
frameCount uint
53+
)
3954

4055
js.Global().Set("runFrame", js.FuncOf(func(this js.Value, args []js.Value) any {
56+
start := time.Now()
4157
frameBuf := args[0]
4258
buttons := args[1].Int()
4359

@@ -47,24 +63,40 @@ func main() {
4763
frame := nes.Frame()
4864
frameBytes := unsafe.Slice((*byte)(unsafe.Pointer(&frame[0])), len(frame)*4)
4965
js.CopyBytesToJS(frameBuf, frameBytes) // TODO: can we avoid copying here?
50-
joy1.SetButtons(uint8(buttons))
66+
joystick.SetButtons(uint8(buttons))
5167
break
5268
}
5369
}
5470

71+
if debugFrameTime {
72+
frameTimeSum += time.Since(start)
73+
frameCount++
74+
75+
if frameCount%120 == 0 {
76+
runtime.ReadMemStats(&mem)
77+
elapsed := frameTimeSum / time.Duration(frameCount)
78+
log.Printf("[DEBUG] frame time: %s, memory: %d", elapsed, mem.Alloc)
79+
frameTimeSum = 0
80+
frameCount = 0
81+
}
82+
}
83+
5584
return nil
5685
}))
5786

5887
js.Global().Set("uploadROM", js.FuncOf(func(this js.Value, args []js.Value) any {
5988
data := js.Global().Get("Uint8Array").New(args[0])
6089
romData := make([]byte, data.Length())
6190
js.CopyBytesToGo(romData, data)
62-
nes = createSystem(joy1, romData)
63-
return nil
64-
}))
6591

66-
js.Global().Set("getAudioSample", js.FuncOf(func(this js.Value, args []js.Value) any {
67-
return nes.AudioSample()
92+
nes2, err := createSystem(joystick, romData)
93+
if err != nil {
94+
log.Printf("[ERROR] failed to initialize: %v", err)
95+
return false
96+
}
97+
98+
nes = nes2
99+
return true
68100
}))
69101

70102
select {}

cpu/official.go

Lines changed: 48 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -212,79 +212,71 @@ func eor(cpu *CPU, mem Memory, arg operand) {
212212
}
213213

214214
func asl(cpu *CPU, mem Memory, arg operand) {
215-
var (
216-
write = func(v uint8) { mem.Write(arg.addr, v) }
217-
read = func() uint8 { return mem.Read(arg.addr) }
218-
)
219-
220215
if arg.mode == AddrModeAcc {
221-
write = func(v uint8) { cpu.A = v }
222-
read = func() uint8 { return cpu.A }
216+
data := cpu.A
217+
cpu.setFlag(flagCarry, data&0x80 != 0)
218+
data <<= 1
219+
cpu.setZN(data)
220+
cpu.A = data
221+
} else {
222+
data := mem.Read(arg.addr)
223+
cpu.setFlag(flagCarry, data&0x80 != 0)
224+
data <<= 1
225+
cpu.setZN(data)
226+
mem.Write(arg.addr, data)
223227
}
224-
225-
data := read()
226-
cpu.setFlag(flagCarry, data&0x80 != 0)
227-
data <<= 1
228-
cpu.setZN(data)
229-
write(data)
230228
}
231229

232230
func lsr(cpu *CPU, mem Memory, arg operand) {
233-
var (
234-
write = func(v uint8) { mem.Write(arg.addr, v) }
235-
read = func() uint8 { return mem.Read(arg.addr) }
236-
)
237-
238231
if arg.mode == AddrModeAcc {
239-
write = func(v uint8) { cpu.A = v }
240-
read = func() uint8 { return cpu.A }
232+
data := cpu.A
233+
cpu.setFlag(flagCarry, data&0x01 != 0)
234+
data >>= 1
235+
cpu.setZN(data)
236+
cpu.A = data
237+
} else {
238+
data := mem.Read(arg.addr)
239+
cpu.setFlag(flagCarry, data&0x01 != 0)
240+
data >>= 1
241+
cpu.setZN(data)
242+
mem.Write(arg.addr, data)
241243
}
242-
243-
data := read()
244-
cpu.setFlag(flagCarry, data&0x01 != 0)
245-
data >>= 1
246-
cpu.setZN(data)
247-
write(data)
248244
}
249245

250246
func rol(cpu *CPU, mem Memory, arg operand) {
251-
var (
252-
write = func(v uint8) { mem.Write(arg.addr, v) }
253-
read = func() uint8 { return mem.Read(arg.addr) }
254-
)
255-
256247
if arg.mode == AddrModeAcc {
257-
write = func(v uint8) { cpu.A = v }
258-
read = func() uint8 { return cpu.A }
248+
data := cpu.A
249+
carr := cpu.carried()
250+
cpu.setFlag(flagCarry, data&0x80 != 0)
251+
data = data<<1 | carr
252+
cpu.setZN(data)
253+
cpu.A = data
254+
} else {
255+
data := mem.Read(arg.addr)
256+
carr := cpu.carried()
257+
cpu.setFlag(flagCarry, data&0x80 != 0)
258+
data = data<<1 | carr
259+
cpu.setZN(data)
260+
mem.Write(arg.addr, data)
259261
}
260-
261-
data := read()
262-
carr := cpu.carried()
263-
264-
cpu.setFlag(flagCarry, data&0x80 != 0)
265-
data = data<<1 | carr
266-
cpu.setZN(data)
267-
write(data)
268262
}
269263

270264
func ror(cpu *CPU, mem Memory, arg operand) {
271-
var (
272-
write = func(v uint8) { mem.Write(arg.addr, v) }
273-
read = func() uint8 { return mem.Read(arg.addr) }
274-
)
275-
276265
if arg.mode == AddrModeAcc {
277-
write = func(v uint8) { cpu.A = v }
278-
read = func() uint8 { return cpu.A }
266+
data := cpu.A
267+
carr := cpu.carried()
268+
cpu.setFlag(flagCarry, data&0x01 != 0)
269+
data = data>>1 | carr<<7
270+
cpu.setZN(data)
271+
cpu.A = data
272+
} else {
273+
data := mem.Read(arg.addr)
274+
carr := cpu.carried()
275+
cpu.setFlag(flagCarry, data&0x01 != 0)
276+
data = data>>1 | carr<<7
277+
cpu.setZN(data)
278+
mem.Write(arg.addr, data)
279279
}
280-
281-
data := read()
282-
carr := cpu.carried()
283-
284-
cpu.setFlag(flagCarry, data&0x01 != 0)
285-
data = data>>1 | carr<<7
286-
cpu.setZN(data)
287-
write(data)
288280
}
289281

290282
func bit(cpu *CPU, mem Memory, arg operand) {

netplay/connect.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build !wasm
2+
13
package netplay
24

35
import (

netplay/wasm.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//go:build wasm
2+
3+
package netplay
4+
5+
import (
6+
"net"
7+
)
8+
9+
func Listen(protocol string, lAddr string, game *Game) (*Netplay, net.Addr, error) {
10+
panic("not implemented for wasm")
11+
}
12+
13+
func Connect(protocol string, rAddr, lAddr string, game *Game) (*Netplay, net.Addr, error) {
14+
panic("not implemented for wasm")
15+
}

web/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
<div class="info-section">
2121
<div class="rom-upload">
22-
Select ROM: <input type="file" id="file-input" accept=".nes">
22+
Select ROM (.nes): <input type="file" id="file-input" accept=".nes">
2323
</div>
2424

2525
<div class="controls-grid">

web/main.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
const go = new Go();
2-
31
const documentReady = new Promise((resolve) => {
42
if (document.readyState !== "loading") {
53
resolve();
@@ -8,8 +6,11 @@ const documentReady = new Promise((resolve) => {
86
}
97
});
108

9+
let wasm = null;
10+
const go = new Go();
1111
const wasmReady = WebAssembly.instantiateStreaming(fetch("dendy.wasm"), go.importObject).then((result) => {
12-
go.run(result.instance);
12+
wasm = result.instance;
13+
go.run(wasm);
1314
});
1415

1516
Promise.all([wasmReady, documentReady]).then(() => {
@@ -65,14 +66,30 @@ Promise.all([wasmReady, documentReady]).then(() => {
6566
}
6667
});
6768

68-
document.getElementById("file-input").addEventListener("input", function (){
69+
let fileInput = document.getElementById("file-input");
70+
71+
fileInput.addEventListener("input", function() {
6972
this.files[0].arrayBuffer().then((buffer) => {
7073
let rom = new Uint8Array(buffer);
71-
uploadROM(rom);
74+
let ok = uploadROM(rom);
75+
if (!ok) {
76+
alert("Invalid ROM file");
77+
this.value = "";
78+
}
7279
});
7380
this.blur();
7481
});
7582

83+
if (fileInput.files.length > 0) {
84+
fileInput.files[0].arrayBuffer().then((buffer) => {
85+
let rom = new Uint8Array(buffer);
86+
let ok = uploadROM(rom);
87+
if (!ok) {
88+
fileInput.value = "";
89+
}
90+
});
91+
}
92+
7693
setInterval(() => {
7794
runFrame(imageData.data, buttonsPressed);
7895
ctx.putImageData(imageData, 0, 0);

0 commit comments

Comments
 (0)