aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2022-12-28 16:17:06 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:43 +0100
commit41d16fb4910fc0402b6b081302c953f7dabc8cf9 (patch)
tree334507bffb28b11760907e04c95602b9fa29efb8
parent7f7389422834f628c9e45278e90fab6911173b25 (diff)
downloadmuhqs-game-41d16fb4910fc0402b6b081302c953f7dabc8cf9.tar.gz
muhqs-game-41d16fb4910fc0402b6b081302c953f7dabc8cf9.zip
add random tile selection from map to the web tools
-rw-r--r--go/.gitignore1
-rw-r--r--go/Makefile11
-rw-r--r--go/game/map.go75
-rw-r--r--go/game/map_test.go10
-rw-r--r--go/webtools/main.go45
-rw-r--r--html/Makefile19
-rw-r--r--html/tools.html62
-rw-r--r--html/wasm_exec.js554
8 files changed, 730 insertions, 47 deletions
diff --git a/go/.gitignore b/go/.gitignore
index eff64396..6e729d48 100644
--- a/go/.gitignore
+++ b/go/.gitignore
@@ -1,3 +1,4 @@
client/client
client/client.wasm
server/server
+webtools/webtools.wasm
diff --git a/go/Makefile b/go/Makefile
index 220a1022..0744d6c9 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -1,10 +1,11 @@
-PACKAGES := assets game server client
+PACKAGES := assets game server client webtools
APPS := server client
OBJS := $(foreach app,$(APPS),$(app)/$(app))
+WASM := client/client.wasm webtools/webtools.wasm
-.PHONY: all fmt test server/server client/client client/client.wasm
-all: $(OBJS)
+.PHONY: all fmt test $(OBJS) $(WASM)
+all: $(OBJS) $(WASM)
server/server:
@echo Building Server ...
@@ -18,6 +19,10 @@ client/client.wasm:
@echo Building Client.wasm ...
pushd $(@D) >/dev/null; env GOOS=js GOARCH=wasm go build -o $(@F); popd >/dev/null
+webtools/webtools.wasm:
+ @echo Building Webtools.wasm ...
+ pushd $(@D) >/dev/null; env GOOS=js GOARCH=wasm go build -o $(@F); popd >/dev/null
+
fmt:
for package in $(PACKAGES); do pushd $$package >/dev/null; go fmt; popd >/dev/null; done
diff --git a/go/game/map.go b/go/game/map.go
index 0b6fe87c..cecce9ca 100644
--- a/go/game/map.go
+++ b/go/game/map.go
@@ -2,12 +2,15 @@ package game
import (
"fmt"
+ "errors"
"io"
"log"
+ "math/rand"
"net/http"
"os"
"path"
"strings"
+ "time"
"gopkg.in/yaml.v3"
)
@@ -27,25 +30,30 @@ type MapYml struct {
type Map struct {
Tiles [][]Tile
+ symbols map[string]string
RessourceGain int
}
-func parseMap(r io.Reader) (*Map, error) {
+func readMap(r io.Reader) (*Map, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
+ return ParseMap(data)
+}
+
+func ParseMap(data []byte) (*Map, error) {
mapYml := MapYml{}
- err = yaml.Unmarshal(data, &mapYml)
+ err := yaml.Unmarshal(data, &mapYml)
if err != nil {
return nil, err
}
- m := &Map{[][]Tile{}, DEFAULT_RESSOURCE_GAIN}
-
mapYml.Symbols[" "] = "neutral"
+ m := &Map{[][]Tile{}, mapYml.Symbols, DEFAULT_RESSOURCE_GAIN}
+
rows := strings.Split(mapYml.Map, "\n")
for y := 0; y < len(rows); y++ {
m.Tiles = append(m.Tiles, []Tile{})
@@ -111,19 +119,19 @@ func (m *Map) FindAnyConnections(x, y int) (connections int, left, right, above,
leftT, rightT, aboveT, belowT := m.FindNeighbours(x, y)
connections = 0
- if leftT.Type != TileTypes.Neutral {
+ if leftT != nil && leftT.Type != TileTypes.Neutral {
left = true
connections += 1
}
- if rightT.Type != TileTypes.Neutral {
+ if rightT != nil && rightT.Type != TileTypes.Neutral {
right = true
connections += 1
}
- if aboveT.Type != TileTypes.Neutral {
+ if aboveT != nil && aboveT.Type != TileTypes.Neutral {
above = true
connections += 1
}
- if belowT.Type != TileTypes.Neutral {
+ if belowT != nil && belowT.Type != TileTypes.Neutral {
below = true
connections += 1
}
@@ -135,19 +143,19 @@ func (m *Map) FindStreetConnections(x int, y int) (connections int, left, right,
leftT, rightT, aboveT, belowT := m.FindNeighbours(x, y)
connections = 0
- if leftT.Type == TileTypes.Street {
+ if leftT != nil && leftT.Type == TileTypes.Street {
left = true
connections += 1
}
- if rightT.Type == TileTypes.Street {
+ if rightT != nil && rightT.Type == TileTypes.Street {
right = true
connections += 1
}
- if aboveT.Type == TileTypes.Street {
+ if aboveT != nil && aboveT.Type == TileTypes.Street {
above = true
connections += 1
}
- if belowT.Type == TileTypes.Street {
+ if belowT != nil && belowT.Type == TileTypes.Street {
below = true
connections += 1
}
@@ -163,19 +171,56 @@ func GetMap(name string) (*Map, error) {
log.Fatal(err)
}
defer resp.Body.Close()
- return parseMap(resp.Body)
+ return readMap(resp.Body)
}
func LoadMap(file string) (*Map, error) {
- f, err := os.Open("/tmp/123")
+ f, err := os.Open(file)
if err != nil {
log.Fatal("Opening map ", file, " failed: ", err)
}
defer f.Close()
- return parseMap(f)
+ return readMap(f)
}
func (m *Map) TileAt(p Position) *Tile {
return &m.Tiles[p.Y][p.X]
}
+
+func (m *Map) RandomTile(types []TileType) Position {
+ candidates := []Position{}
+ for y := 0; y < len(m.Tiles); y++ {
+ for x := 0; x < len(m.Tiles[y]); x++ {
+ candidate := m.Tiles[y][x]
+ for _, t := range types {
+ if candidate.Type == t {
+ candidates = append(candidates, candidate.Position)
+ break
+ }
+ }
+ }
+ }
+
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ randIdx := r.Intn(len(candidates))
+ return candidates[randIdx]
+}
+
+func (m *Map) RandomTileFromSymbols(symbols []string) (Position, error) {
+ types := make([]TileType, 0, len(symbols))
+ for _, symbol := range symbols {
+ tileString, ok := m.symbols[symbol]
+ if !ok {
+ return INVALID_POSITION(), errors.New(fmt.Sprintf("symbol %s not in the map symbols %v", symbol, m.symbols))
+ }
+
+ tileType, ok := TileNames[tileString]
+ if !ok {
+ return INVALID_POSITION(), errors.New("tile type unknown")
+ }
+
+ types = append(types, tileType)
+ }
+ return m.RandomTile(types), nil
+}
diff --git a/go/game/map_test.go b/go/game/map_test.go
index 732c8b86..8212f648 100644
--- a/go/game/map_test.go
+++ b/go/game/map_test.go
@@ -30,7 +30,7 @@ kings:
- (9,1)`
r := strings.NewReader(s)
- m, err := parseMap(r)
+ m, err := readMap(r)
if err != nil {
t.Fatalf("Parsing failed with: %v", err)
}
@@ -51,3 +51,11 @@ kings:
t.Fatalf("ressource gain is not the default one")
}
}
+
+func TestLoadingAllMaps(T *testing.T) {
+ maps := []string{"the-tyrant", "2P-ring-street", "2P-river-king", "3P-ring-street", "4P-islands", "the-kraken"}
+
+ for _, m := range maps {
+ LoadMap("../../maps/" + m + ".yml")
+ }
+}
diff --git a/go/webtools/main.go b/go/webtools/main.go
new file mode 100644
index 00000000..03fc9e37
--- /dev/null
+++ b/go/webtools/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "syscall/js"
+
+ "muhq.space/muhqs-game/go/game"
+)
+
+const ()
+
+func jSRandomTileFromMap() js.Func {
+ jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
+ if len(args) != 2 {
+ return "Invalid number of arguments passed"
+ }
+
+ mapData := args[0].String()
+
+ symbols := make([]string, 0, args[1].Length())
+ for i := 0; i < args[1].Length(); i++ {
+ symbol := args[1].Index(i).String()
+ symbols = append(symbols, symbol)
+ }
+
+ position, err := randomTileFromMap(mapData, symbols)
+ if err != nil {
+ return err.Error()
+ }
+ return map[string]interface{}{"y": position.Y + 1, "x": position.X + 1}
+ })
+ return jsonFunc
+}
+
+func randomTileFromMap(mapData string, symbols []string) (game.Position, error) {
+ m, err := game.ParseMap([]byte(mapData))
+ if err != nil {
+ return game.INVALID_POSITION(), err
+ }
+ return m.RandomTileFromSymbols(symbols)
+}
+
+func main() {
+ js.Global().Set("randomTileFromSymbol", jSRandomTileFromMap())
+ <-make(chan bool)
+}
diff --git a/html/Makefile b/html/Makefile
index 3c969595..9188c4a5 100644
--- a/html/Makefile
+++ b/html/Makefile
@@ -10,6 +10,7 @@ MAKEFILE_DIR := $(dir $(MAKEFILE_PATH))
HTML_ROOT := $(MAKEFILE_DIR)
GAME_ROOT := $(MAKEFILE_DIR)..
+GO_ROOT := $(GAME_ROOT)/go
RULES_ROOT := $(GAME_ROOT)/rules
MAPS_ROOT := $(GAME_ROOT)/maps
DATA_ROOT := $(GAME_ROOT)/data
@@ -55,12 +56,21 @@ CARDS_YAML := $(foreach dir,$(SETS_SOURCEDIRS),$(wildcard $(dir)/*.yml))
MAKEFILE_LIST := Makefile
+FILES_TO_COPY := wasm_exec.js hover-placeholder.png tools.html
+FILES_TO_COPY_TARGETS := $(addprefix $(BUILDDIR)/,$(FILES_TO_COPY))
+
.PHONY: all clean maps misc
all: $(HTML) $(RULES_HTML) maps misc $(BLOG_POSTS_HTML)
-misc: $(BUILDDIR)/latex-build $(BUILDDIR)/cards-data $(BUILDDIR)/hover-placeholder.png
+misc: $(BUILDDIR)/latex-build $(BUILDDIR)/cards-data $(FILES_TO_COPY_TARGETS) \
+ $(BUILDDIR)/webtools.wasm
+
+$(BUILDDIR)/webtools.wasm:
+ @echo "creating symlink $@"
+ @if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi
+ ln -s $(GO_ROOT)/webtools/webtools.wasm $@
-$(BUILDDIR)/hover-placeholder.png: hover-placeholder.png
+$(FILES_TO_COPY_TARGETS): $(BUILDDIR)/%: %
@echo "copying $@"
@if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi
$(VERBOSE) cp $< $@
@@ -73,11 +83,6 @@ maps:
$(VERBOSE) ln -sfT $(MAPS_ROOT) $(BUILDDIR)/maps
$(VERBOSE) $(MAKE) -C $(MAPS_ROOT)
-$(BUILDDIR)/tools.html: tools.html
- @echo "copying $@"
- @if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi
- $(VERBOSE) cp $< $@
-
$(BUILDDIR)/latex-build:
@echo "render cards"
$(VERBOSE) $(MAKE) -C $(LATEX_ROOT)
diff --git a/html/tools.html b/html/tools.html
index 5d638ef5..caca608e 100644
--- a/html/tools.html
+++ b/html/tools.html
@@ -68,6 +68,15 @@ body {
}
</style>
+ <script src="wasm_exec.js"></script>
+
+ <script>
+ const go = new Go();
+ WebAssembly.instantiateStreaming(fetch("webtools.wasm"), go.importObject).then((result) => {
+ go.run(result.instance);
+ });
+ </script>
+
<script language="javascript" type="text/javascript">
/*
@licstart The following is the entire license notice for the
@@ -93,19 +102,6 @@ body {
for the JavaScript code in this page.
*/
- // List of houses on the tyrant map
- let tyrantHouses = Array(
- {x:1, y:1}, {x:4, y:1}, {x:5, y:1}, {x:8, y:1}, {x:15, y:1},
- {x:3, y:2}, {x:6, y:2}, {x:12, y:2}, {x:13, y:2}, {x:14, y:2},
- {x:2, y:3}, {x:14, y:3},
- {x:2, y:4}, {x:5, y:4}, {x:10, y:4},
- {x:3, y:5}, {x:6, y:4}, {x:9, y:4},
- {x:7, y:6}, {x:10, y:6},
- {x:13, y:10},
- {x:4, y:11},
- {x:3, y:15}, {x:9, y:15}, {x:14, y:15},
- );
-
// return a random number between 1 and max
function randomInt(max) {
return Math.floor(Math.random() * Number(max)) + 1;
@@ -200,11 +196,26 @@ body {
return false;
}
- document.getElementById("random_house_tyrant_form").onsubmit = function() {
- let idx = Math.floor(Math.random() * tyrantHouses.length);
- let house = tyrantHouses[idx];
- result.innerHTML = "Random house: " + (idx + 1) + " at (x:" + house.x + ", y:"
- + house.y + ")";
+ document.getElementById("random_map_tile_form").onsubmit = function() {
+ const mapName = document.getElementById("random_map_tile_map_select").value;
+ const mapUrl = "maps/" + mapName + ".yml"
+ const symbols = document.getElementById("random_map_tile_symbols").value.split(" ");
+ const request = new XMLHttpRequest();
+ request.onreadystatechange = function() {
+ if (request.readyState == 4 && request.status == 200) {
+ const pos = randomTileFromSymbol(request.responseText, symbols);
+ if (!pos) {
+ result.innerHTML = "The Go websassembly returned no value";
+ } else if (typeof pos === "string" || pos instanceof String) {
+ result.innerHTML = "An error occured: " + pos;
+ } else {
+ result.innerHTML = "Random tile: (x:" + pos.x + ", y:" + pos.y + ")";
+ }
+ }
+ }
+ request.open("GET", mapUrl, true);
+ request.send(null);
+
return false;
}
});
@@ -250,9 +261,18 @@ Random tile within Range
<h2>The tyrant tools</h2>
<div>
-<form id="random_house_tyrant_form">
-Random house tile
- <input id="random_house_tyrant_button" value="generate" type="submit">
+<form id="random_map_tile_form">
+Random tile from
+ <select name="map" id="random_map_tile_map_select">
+ <option value="the-tyrant">the-tyrant</option>
+ <option value="2P-ring-street">2P-ring-street</option>
+ <option value="2P-river-king">2P-river-king</option>
+ <option value="3P-ring-street">3P-ring-street</option>
+ <option value="4P-islands">4P-islands</option>
+ <option value="the-kraken">the-kraken</option>
+ </select>
+ <input id="random_map_tile_symbols" value="symbols">
+ <input id="random_map_tile_button" value="generate" type="submit">
</form>
</div>
diff --git a/html/wasm_exec.js b/html/wasm_exec.js
new file mode 100644
index 00000000..9ce6a20c
--- /dev/null
+++ b/html/wasm_exec.js
@@ -0,0 +1,554 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+"use strict";
+
+(() => {
+ const enosys = () => {
+ const err = new Error("not implemented");
+ err.code = "ENOSYS";
+ return err;
+ };
+
+ if (!globalThis.fs) {
+ let outputBuf = "";
+ globalThis.fs = {
+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
+ writeSync(fd, buf) {
+ outputBuf += decoder.decode(buf);
+ const nl = outputBuf.lastIndexOf("\n");
+ if (nl != -1) {
+ console.log(outputBuf.substr(0, nl));
+ outputBuf = outputBuf.substr(nl + 1);
+ }
+ return buf.length;
+ },
+ write(fd, buf, offset, length, position, callback) {
+ if (offset !== 0 || length !== buf.length || position !== null) {
+ callback(enosys());
+ return;
+ }
+ const n = this.writeSync(fd, buf);
+ callback(null, n);
+ },
+ chmod(path, mode, callback) { callback(enosys()); },
+ chown(path, uid, gid, callback) { callback(enosys()); },
+ close(fd, callback) { callback(enosys()); },
+ fchmod(fd, mode, callback) { callback(enosys()); },
+ fchown(fd, uid, gid, callback) { callback(enosys()); },
+ fstat(fd, callback) { callback(enosys()); },
+ fsync(fd, callback) { callback(null); },
+ ftruncate(fd, length, callback) { callback(enosys()); },
+ lchown(path, uid, gid, callback) { callback(enosys()); },
+ link(path, link, callback) { callback(enosys()); },
+ lstat(path, callback) { callback(enosys()); },
+ mkdir(path, perm, callback) { callback(enosys()); },
+ open(path, flags, mode, callback) { callback(enosys()); },
+ read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
+ readdir(path, callback) { callback(enosys()); },
+ readlink(path, callback) { callback(enosys()); },
+ rename(from, to, callback) { callback(enosys()); },
+ rmdir(path, callback) { callback(enosys()); },
+ stat(path, callback) { callback(enosys()); },
+ symlink(path, link, callback) { callback(enosys()); },
+ truncate(path, length, callback) { callback(enosys()); },
+ unlink(path, callback) { callback(enosys()); },
+ utimes(path, atime, mtime, callback) { callback(enosys()); },
+ };
+ }
+
+ if (!globalThis.process) {
+ globalThis.process = {
+ getuid() { return -1; },
+ getgid() { return -1; },
+ geteuid() { return -1; },
+ getegid() { return -1; },
+ getgroups() { throw enosys(); },
+ pid: -1,
+ ppid: -1,
+ umask() { throw enosys(); },
+ cwd() { throw enosys(); },
+ chdir() { throw enosys(); },
+ }
+ }
+
+ if (!globalThis.crypto) {
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
+ }
+
+ if (!globalThis.performance) {
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
+ }
+
+ if (!globalThis.TextEncoder) {
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
+ }
+
+ if (!globalThis.TextDecoder) {
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
+ }
+
+ const encoder = new TextEncoder("utf-8");
+ const decoder = new TextDecoder("utf-8");
+
+ globalThis.Go = class {
+ constructor() {
+ this.argv = ["js"];
+ this.env = {};
+ this.exit = (code) => {
+ if (code !== 0) {
+ console.warn("exit code:", code);
+ }
+ };
+ this._exitPromise = new Promise((resolve) => {
+ this._resolveExitPromise = resolve;
+ });
+ this._pendingEvent = null;
+ this._scheduledTimeouts = new Map();
+ this._nextCallbackTimeoutID = 1;
+
+ const setInt64 = (addr, v) => {
+ this.mem.setUint32(addr + 0, v, true);
+ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
+ }
+
+ const getInt64 = (addr) => {
+ const low = this.mem.getUint32(addr + 0, true);
+ const high = this.mem.getInt32(addr + 4, true);
+ return low + high * 4294967296;
+ }
+
+ const loadValue = (addr) => {
+ const f = this.mem.getFloat64(addr, true);
+ if (f === 0) {
+ return undefined;
+ }
+ if (!isNaN(f)) {
+ return f;
+ }
+
+ const id = this.mem.getUint32(addr, true);
+ return this._values[id];
+ }
+
+ const storeValue = (addr, v) => {
+ const nanHead = 0x7FF80000;
+
+ if (typeof v === "number" && v !== 0) {
+ if (isNaN(v)) {
+ this.mem.setUint32(addr + 4, nanHead, true);
+ this.mem.setUint32(addr, 0, true);
+ return;
+ }
+ this.mem.setFloat64(addr, v, true);
+ return;
+ }
+
+ if (v === undefined) {
+ this.mem.setFloat64(addr, 0, true);
+ return;
+ }
+
+ let id = this._ids.get(v);
+ if (id === undefined) {
+ id = this._idPool.pop();
+ if (id === undefined) {
+ id = this._values.length;
+ }
+ this._values[id] = v;
+ this._goRefCounts[id] = 0;
+ this._ids.set(v, id);
+ }
+ this._goRefCounts[id]++;
+ let typeFlag = 0;
+ switch (typeof v) {
+ case "object":
+ if (v !== null) {
+ typeFlag = 1;
+ }
+ break;
+ case "string":
+ typeFlag = 2;
+ break;
+ case "symbol":
+ typeFlag = 3;
+ break;
+ case "function":
+ typeFlag = 4;
+ break;
+ }
+ this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
+ this.mem.setUint32(addr, id, true);
+ }
+
+ const loadSlice = (addr) => {
+ const array = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ return new Uint8Array(this._inst.exports.mem.buffer, array, len);
+ }
+
+ const loadSliceOfValues = (addr) => {
+ const array = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ const a = new Array(len);
+ for (let i = 0; i < len; i++) {
+ a[i] = loadValue(array + i * 8);
+ }
+ return a;
+ }
+
+ const loadString = (addr) => {
+ const saddr = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
+ }
+
+ const timeOrigin = Date.now() - performance.now();
+ this.importObject = {
+ go: {
+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
+ // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
+ // This changes the SP, thus we have to update the SP used by the imported function.
+
+ // func wasmExit(code int32)
+ "runtime.wasmExit": (sp) => {
+ sp >>>= 0;
+ const code = this.mem.getInt32(sp + 8, true);
+ this.exited = true;
+ delete this._inst;
+ delete this._values;
+ delete this._goRefCounts;
+ delete this._ids;
+ delete this._idPool;
+ this.exit(code);
+ },
+
+ // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
+ "runtime.wasmWrite": (sp) => {
+ sp >>>= 0;
+ const fd = getInt64(sp + 8);
+ const p = getInt64(sp + 16);
+ const n = this.mem.getInt32(sp + 24, true);
+ fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
+ },
+
+ // func resetMemoryDataView()
+ "runtime.resetMemoryDataView": (sp) => {
+ sp >>>= 0;
+ this.mem = new DataView(this._inst.exports.mem.buffer);
+ },
+
+ // func nanotime1() int64
+ "runtime.nanotime1": (sp) => {
+ sp >>>= 0;
+ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
+ },
+
+ // func walltime() (sec int64, nsec int32)
+ "runtime.walltime": (sp) => {
+ sp >>>= 0;
+ const msec = (new Date).getTime();
+ setInt64(sp + 8, msec / 1000);
+ this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
+ },
+
+ // func scheduleTimeoutEvent(delay int64) int32
+ "runtime.scheduleTimeoutEvent": (sp) => {
+ sp >>>= 0;
+ const id = this._nextCallbackTimeoutID;
+ this._nextCallbackTimeoutID++;
+ this._scheduledTimeouts.set(id, setTimeout(
+ () => {
+ this._resume();
+ while (this._scheduledTimeouts.has(id)) {
+ // for some reason Go failed to register the timeout event, log and try again
+ // (temporary workaround for https://github.com/golang/go/issues/28975)
+ console.warn("scheduleTimeoutEvent: missed timeout event");
+ this._resume();
+ }
+ },
+ getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
+ ));
+ this.mem.setInt32(sp + 16, id, true);
+ },
+
+ // func clearTimeoutEvent(id int32)
+ "runtime.clearTimeoutEvent": (sp) => {
+ sp >>>= 0;
+ const id = this.mem.getInt32(sp + 8, true);
+ clearTimeout(this._scheduledTimeouts.get(id));
+ this._scheduledTimeouts.delete(id);
+ },
+
+ // func getRandomData(r []byte)
+ "runtime.getRandomData": (sp) => {
+ sp >>>= 0;
+ crypto.getRandomValues(loadSlice(sp + 8));
+ },
+
+ // func finalizeRef(v ref)
+ "syscall/js.finalizeRef": (sp) => {
+ sp >>>= 0;
+ const id = this.mem.getUint32(sp + 8, true);
+ this._goRefCounts[id]--;
+ if (this._goRefCounts[id] === 0) {
+ const v = this._values[id];
+ this._values[id] = null;
+ this._ids.delete(v);
+ this._idPool.push(id);
+ }
+ },
+
+ // func stringVal(value string) ref
+ "syscall/js.stringVal": (sp) => {
+ sp >>>= 0;
+ storeValue(sp + 24, loadString(sp + 8));
+ },
+
+ // func valueGet(v ref, p string) ref
+ "syscall/js.valueGet": (sp) => {
+ sp >>>= 0;
+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 32, result);
+ },
+
+ // func valueSet(v ref, p string, x ref)
+ "syscall/js.valueSet": (sp) => {
+ sp >>>= 0;
+ Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
+ },
+
+ // func valueDelete(v ref, p string)
+ "syscall/js.valueDelete": (sp) => {
+ sp >>>= 0;
+ Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
+ },
+
+ // func valueIndex(v ref, i int) ref
+ "syscall/js.valueIndex": (sp) => {
+ sp >>>= 0;
+ storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
+ },
+
+ // valueSetIndex(v ref, i int, x ref)
+ "syscall/js.valueSetIndex": (sp) => {
+ sp >>>= 0;
+ Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
+ },
+
+ // func valueCall(v ref, m string, args []ref) (ref, bool)
+ "syscall/js.valueCall": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const m = Reflect.get(v, loadString(sp + 16));
+ const args = loadSliceOfValues(sp + 32);
+ const result = Reflect.apply(m, v, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 56, result);
+ this.mem.setUint8(sp + 64, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 56, err);
+ this.mem.setUint8(sp + 64, 0);
+ }
+ },
+
+ // func valueInvoke(v ref, args []ref) (ref, bool)
+ "syscall/js.valueInvoke": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const args = loadSliceOfValues(sp + 16);
+ const result = Reflect.apply(v, undefined, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, result);
+ this.mem.setUint8(sp + 48, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, err);
+ this.mem.setUint8(sp + 48, 0);
+ }
+ },
+
+ // func valueNew(v ref, args []ref) (ref, bool)
+ "syscall/js.valueNew": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const args = loadSliceOfValues(sp + 16);
+ const result = Reflect.construct(v, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, result);
+ this.mem.setUint8(sp + 48, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, err);
+ this.mem.setUint8(sp + 48, 0);
+ }
+ },
+
+ // func valueLength(v ref) int
+ "syscall/js.valueLength": (sp) => {
+ sp >>>= 0;
+ setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
+ },
+
+ // valuePrepareString(v ref) (ref, int)
+ "syscall/js.valuePrepareString": (sp) => {
+ sp >>>= 0;
+ const str = encoder.encode(String(loadValue(sp + 8)));
+ storeValue(sp + 16, str);
+ setInt64(sp + 24, str.length);
+ },
+
+ // valueLoadString(v ref, b []byte)
+ "syscall/js.valueLoadString": (sp) => {
+ sp >>>= 0;
+ const str = loadValue(sp + 8);
+ loadSlice(sp + 16).set(str);
+ },
+
+ // func valueInstanceOf(v ref, t ref) bool
+ "syscall/js.valueInstanceOf": (sp) => {
+ sp >>>= 0;
+ this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
+ },
+
+ // func copyBytesToGo(dst []byte, src ref) (int, bool)
+ "syscall/js.copyBytesToGo": (sp) => {
+ sp >>>= 0;
+ const dst = loadSlice(sp + 8);
+ const src = loadValue(sp + 32);
+ if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
+ this.mem.setUint8(sp + 48, 0);
+ return;
+ }
+ const toCopy = src.subarray(0, dst.length);
+ dst.set(toCopy);
+ setInt64(sp + 40, toCopy.length);
+ this.mem.setUint8(sp + 48, 1);
+ },
+
+ // func copyBytesToJS(dst ref, src []byte) (int, bool)
+ "syscall/js.copyBytesToJS": (sp) => {
+ sp >>>= 0;
+ const dst = loadValue(sp + 8);
+ const src = loadSlice(sp + 16);
+ if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
+ this.mem.setUint8(sp + 48, 0);
+ return;
+ }
+ const toCopy = src.subarray(0, dst.length);
+ dst.set(toCopy);
+ setInt64(sp + 40, toCopy.length);
+ this.mem.setUint8(sp + 48, 1);
+ },
+
+ "debug": (value) => {
+ console.log(value);
+ },
+ }
+ };
+ }
+
+ async run(instance) {
+ if (!(instance instanceof WebAssembly.Instance)) {
+ throw new Error("Go.run: WebAssembly.Instance expected");
+ }
+ this._inst = instance;
+ this.mem = new DataView(this._inst.exports.mem.buffer);
+ this._values = [ // JS values that Go currently has references to, indexed by reference id
+ NaN,
+ 0,
+ null,
+ true,
+ false,
+ globalThis,
+ this,
+ ];
+ this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
+ this._ids = new Map([ // mapping from JS values to reference ids
+ [0, 1],
+ [null, 2],
+ [true, 3],
+ [false, 4],
+ [globalThis, 5],
+ [this, 6],
+ ]);
+ this._idPool = []; // unused ids that have been garbage collected
+ this.exited = false; // whether the Go program has exited
+
+ // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
+ let offset = 4096;
+
+ const strPtr = (str) => {
+ const ptr = offset;
+ const bytes = encoder.encode(str + "\0");
+ new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
+ offset += bytes.length;
+ if (offset % 8 !== 0) {
+ offset += 8 - (offset % 8);
+ }
+ return ptr;
+ };
+
+ const argc = this.argv.length;
+
+ const argvPtrs = [];
+ this.argv.forEach((arg) => {
+ argvPtrs.push(strPtr(arg));
+ });
+ argvPtrs.push(0);
+
+ const keys = Object.keys(this.env).sort();
+ keys.forEach((key) => {
+ argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
+ });
+ argvPtrs.push(0);
+
+ const argv = offset;
+ argvPtrs.forEach((ptr) => {
+ this.mem.setUint32(offset, ptr, true);
+ this.mem.setUint32(offset + 4, 0, true);
+ offset += 8;
+ });
+
+ // The linker guarantees global data starts from at least wasmMinDataAddr.
+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
+ const wasmMinDataAddr = 4096 + 8192;
+ if (offset >= wasmMinDataAddr) {
+ throw new Error("total length of command line and environment variables exceeds limit");
+ }
+
+ this._inst.exports.run(argc, argv);
+ if (this.exited) {
+ this._resolveExitPromise();
+ }
+ await this._exitPromise;
+ }
+
+ _resume() {
+ if (this.exited) {
+ throw new Error("Go program has already exited");
+ }
+ this._inst.exports.resume();
+ if (this.exited) {
+ this._resolveExitPromise();
+ }
+ }
+
+ _makeFuncWrapper(id) {
+ const go = this;
+ return function () {
+ const event = { id: id, this: this, args: arguments };
+ go._pendingEvent = event;
+ go._resume();
+ return event.result;
+ };
+ }
+ }
+})();