Help us shape the future of Go! Take the 2026 Go Developer Survey

playground

package module
v0.0.0-...-dd6fb2f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

go-ndarray playground

The page at https://go-ndarray.github.io/playground/: go-ndarray itself, compiled to WebAssembly, inside a go-widgets canvas application. Nothing is computed in JavaScript — the page only loads the wasm, forwards input and follows the landing's theme toggle. The Ruby tab runs its program on go-embedded-ruby, a Ruby interpreter in Go, compiled to WebAssembly too and fetched only when the tab is opened.

A visitor builds a pipeline of steps — create an array (Arange, Linspace, Zeros/Ones/Full, Eye, typed-in FromData), then apply views (Reshape with -1, Transpose, NumPy-style Slice), broadcasting arithmetic, ufuncs, reductions along an axis, Concatenate/Stack, MatMul/Dot/Outer — and sees for each step its shape, its strides (in elements and in bytes), whether it is a view sharing memory with earlier steps, the time one call took in the browser, its values as a heatmap table, and the whole pipeline as a Go program that compiles, a Ruby program and its NumPy equivalent (all three copyable). The Ruby program uses go-embedded-ruby's NDArray, which is go-ndarray bound into the interpreter; Run executes it in the page and prints every step, then the selected step's shape and to_a. Errors are go-ndarray's own (ndarray: shapes are not broadcastable: [2 3] vs [3 2]).

Layout

package role
engine the model: the operation catalog, argument parsing, running a step (timed), its Go, Ruby and NumPy spellings, and the layout of a result read from go-ndarray's Array
vm the ViewModel: every piece of state as a go-widgets/mvvm Observable, ObservableList or Command; no widget
. (playground) the View: toolkit widgets in box layouts, bound to the ViewModel (mvvmtk, plus view_binding.go); event routing through the root box
cmd/playground-wasm the browser shell: canvas at device pixels, input forwarding, ndarraySetTheme, ndarrayDebug/ndarrayRects for the proofs, and the Ruby hooks (ndarrayRubyLoad, ndarrayRubyEval around rbgoEval)
rubywasm a module of its own that only pins go-embedded-ruby (tool directive): the deploy builds its cmd/wasm from here, and Renovate bumps the pin

Widgets used: Label, DropDown, ListBox, Entry, Button, ViewSwitcher, TextView, Table, ProgressBar, VBox/HBox. The heatmap is Table.CellFill with toolkit.Viridis, added to go-widgets/toolkit for this page.

Strides are not exported by go-ndarray v0.7.1; engine.LayoutOf reads the strides, offset and data fields through reflect (read-only, no unsafe), and TestLayoutReadsTheLibrary fails if a release renames them.

go-ndarray's helper goroutines under WebAssembly

go-ndarray keeps up to GOMAXPROCS-1 helper goroutines alive for large operations (its README, "Goroutines"). Under GOOS=js the Go runtime has a single P: GOMAXPROCS is 1, every operation takes go-ndarray's serial path, and no helper goroutine is ever started. engine/goroutines_test.go checks it both ways: run as WebAssembly under node, the goroutine count is unchanged after 10⁶-element Exp, Sum and a 1000² MatMul; run natively with GOMAXPROCS=4, the same steps start three helpers (the positive control). The browser proof repeats the WebAssembly check in Chrome. The timings the page shows are therefore single-threaded, and without SIMD (wasm has no go-asmgen kernel), so they are slower than native go-ndarray.

Build and test

GOWORK=off go test -race ./...                       # 100% statement coverage in CI
GOWORK=off GOOS=js GOARCH=wasm go test \
  -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" ./engine
GOOS=js GOARCH=wasm go build -o ../static/playground/playground.wasm ./cmd/playground-wasm
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ../static/playground/

The wasm is about 13 MB (3.4 MB gzipped, as Pages serves it). Neither it nor wasm_exec.js is committed: the deploy builds both from this commit.

The browser proof (browser_test.go + browsertest/driver.cjs) builds the site with Hugo and drives it in headless Chrome:

CHROME=… PLAYGROUND_NODE_PATH=<dir with puppeteer-core> HUGO=hugo \
  GOWORK=off go test -run TestBrowserProof -v .

Screenshots go to $PLAYGROUND_CAPTURE_DIR, else the user config directory — never into a repository (the helper refuses a directory inside a git tree).

Ruby: go-embedded-ruby in the same page

The Ruby tab's program is written against go-embedded-ruby's NDArray (NDArray.arange(0, 6, 1), a.reshape(2, -1), b[true, (1..)], b.sum(0), NDArray.concatenate([a, b], axis: 0), …); engine emits nothing else, and engine/ruby_test.go checks every operation's spelling and that it calls only the methods of that API.

The interpreter is a second Go program compiled to WebAssembly, github.com/go-embedded-ruby/ruby/cmd/wasm, at the version rubywasm/go.mod pins, built with the rbgo_core tag: the Ruby language and its numeric stack (NDArray, FFT, Complex, Rational, Set, BigDecimal, Matrix, …) and none of the gem bindings, which the playground never calls:

cd rubywasm
GOOS=js GOARCH=wasm go build -tags rbgo_core -trimpath -ldflags="-s -w" \
  -o ../../static/playground/rbgo.wasm github.com/go-embedded-ruby/ruby/cmd/wasm

The full build was about 100 MB (22 MB gzipped); the core one is about 23 MB (5.7 MB gzipped). It is still larger than the playground, so the page never loads it up front: the first time the visitor opens the Ruby tab, the ViewModel asks the host, the page's ndarrayRubyLoad fetches it with a progress line and bar counting the bytes, and starts it in its own Go runtime (a second new Go() with the same wasm_exec.js: both modules are built by one toolchain). Run then calls its rbgoEval(src) and shows the output.

The browser proof counts the requests for rbgo.wasm: none through the Go and NumPy tabs, one when the Ruby tab opens, still one after two runs. It runs the README example in Ruby and requires the printed shape and to_a to equal go-ndarray's result for the same step. TestRubyAgreesWithGo (in engine) goes further, without a browser: it runs the Ruby program of every operation case and of every step of every preset on the interpreter under node (engine/testdata/rbeval.cjs) and compares each printed shape and value with go-ndarray's. Both read PLAYGROUND_RUBY_WASM=<file>, a prebuilt interpreter (the proof builds the pinned one without it; CI builds it once for both).

Documentation

Overview

Package playground is the View of the go-ndarray playground: a go-widgets canvas application compiled to WebAssembly. It builds toolkit widgets, lays them out in boxes and binds each one to the ViewModel (package vm) — it draws nothing by hand and holds no state of its own beyond layout and pointer capture. cmd/playground-wasm is the browser shell around it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetupText

func SetupText(scale float64)

SetupText selects the toolkit's anti-aliased text at the device scale, so one logical pixel is one CSS pixel and text is crisp on a HiDPI screen. It must run before the first layout measures text.

func Theme

func Theme(dark bool) *toolkit.Theme

Theme returns the playground palette: the toolkit defaults with the landing's neutral backgrounds and its emerald accent (go-ndarray.github.io's [params.brand]: base #047857 in light, bright #34d399 in dark).

Types

type Debug

type Debug struct {
	Steps    []string `json:"steps"`
	Selected int      `json:"selected"`
	Op       string   `json:"op"`
	Input    string   `json:"input"`
	Args     string   `json:"args"`
	Error    string   `json:"error"`
	Title    string   `json:"title"`
	Shape    string   `json:"shape"`
	Memory   string   `json:"memory"`
	Timing   string   `json:"timing"`
	Rows     int      `json:"rows"`
	Cols     int      `json:"cols"`
	Dark     bool     `json:"dark"`
	Status   string   `json:"status"`
	Code     string   `json:"code"`
	Ops      []string `json:"ops"`
	Presets  []string `json:"presets"`
	// The code panel's language (0 Go, 1 Ruby, 2 NumPy) and the Ruby run: its
	// status line, download fraction and output.
	Lang         int     `json:"lang"`
	RubyStatus   string  `json:"rubyStatus"`
	RubyProgress float64 `json:"rubyProgress"`
	RubyOutput   string  `json:"rubyOutput"`
	CanRunRuby   bool    `json:"canRunRuby"`
	// The selected step's result as go-ndarray computed it: its shape and its
	// values in row-major order, for comparing with what Ruby printed.
	ResultShape  []int     `json:"resultShape"`
	ResultValues []float64 `json:"resultValues"`
	// Goroutines and MaxProcs let a proof watch go-ndarray's helper pool
	// under GOOS=js: GOMAXPROCS is 1 there, so no helper should ever start.
	Goroutines int `json:"goroutines"`
	MaxProcs   int `json:"maxProcs"`
}

Debug is a snapshot of what the scene shows, for the browser proofs: they assert on it after driving the page with real mouse and keyboard events.

type State

type State struct {
	VM *vm.ViewModel
	// contains filtered or unexported fields
}

State is the View: the widgets, their layout, the ViewModel they are bound to, and the pointer capture.

func NewState

func NewState(w, h int, dark bool) *State

NewState builds the View over a fresh ViewModel, w×h device pixels.

func (*State) Dark

func (s *State) Dark() bool

Dark reports whether the dark palette is active.

func (*State) Debug

func (s *State) Debug() Debug

Debug reports the scene's current state.

func (*State) Dirty

func (s *State) Dirty() bool

Dirty reports whether something changed since the last Draw.

func (*State) Draw

func (s *State) Draw(buf []byte)

Draw paints the whole scene into buf (w×h RGBA). Open popovers are drawn last so they float above the widgets under them.

func (*State) HandleChar

func (s *State) HandleChar(ch string) bool

HandleChar types a printable character into the focused widget.

func (*State) HandleClick

func (s *State) HandleClick(x, y int) bool

HandleClick routes a primary-button press at surface (x, y).

func (*State) HandleKeyDown

func (s *State) HandleKeyDown(code string) bool

HandleKeyDown delivers a named key (Enter, Backspace, arrows, Tab, Escape) to the focused widget; Escape also closes any open popover.

func (*State) HandleMove

func (s *State) HandleMove(x, y int) bool

HandleMove routes a pointer move: a drag of the captured widget (a scrollbar thumb) while the button is held, else a hover.

func (*State) HandleRelease

func (s *State) HandleRelease(x, y int) bool

HandleRelease ends a drag.

func (*State) HandleScroll

func (s *State) HandleScroll(x, y, dx, dy int) bool

HandleScroll routes a wheel of dy rows (dx sideways) at (x, y): to an open popover's list first, else to the widget under the pointer.

func (*State) MinHeight

func (s *State) MinHeight(w int) int

MinHeight is the least height, in device pixels, the scene needs at width w. Side by side it is 0: every widget fits whatever the window gives. Stacked, the form, the summary and the code panel have fixed heights and the table takes what is left, so a short window left it nothing: 0 px on a 390×844 phone. The host makes the canvas at least this tall and lets the page scroll.

It is measured, not computed: in the stacked layout the table is the only part that grows, so the layout is laid out once at a tall height and the table's share is replaced by minTableH. Formula and layout cannot drift.

func (*State) Rects

func (s *State) Rects() map[string]toolkit.Rect

Rects names the rectangles of the widgets a test clicks, in device pixels.

func (*State) Resize

func (s *State) Resize(w, h int)

Resize lays the scene out for w×h device pixels.

func (*State) SetTheme

func (s *State) SetTheme(dark bool)

SetTheme switches between the light and the dark palette.

func (*State) Size

func (s *State) Size() (int, int)

Size is the scene size in device pixels.

Directories

Path Synopsis
cmd
playground-wasm command
Package engine is the model of the go-ndarray playground: a pipeline of steps, each one go-ndarray operation applied to earlier results, with the Go, Ruby and NumPy spellings of every step and what each result looks like in memory.
Package engine is the model of the go-ndarray playground: a pipeline of steps, each one go-ndarray operation applied to earlier results, with the Go, Ruby and NumPy spellings of every step and what each result looks like in memory.
Package vm is the ViewModel of the go-ndarray playground.
Package vm is the ViewModel of the go-ndarray playground.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL