Package github.com/matjam/bunyip/console
console
Package console is the engine's in-game debug console: a drop-down command line over the top of the view, and a window of panels that show what every part of the engine is doing.
To turn it on, set Config.Console. The engine draws it after the game and the debug overlay.
The engine makes the console, attaches what it owns (the graphics context, the mixer, the input state and the frame timings) and hands it to the game on Context.Console. A game that wants a console of its own shape calls New instead and calls its Draw method explicitly.
The console
The backquote key opens and closes the drop-down (Options.Key chooses another). While it is open it takes the keyboard: the game asks Console.Open and skips its own key handling for that update. The panel holds the log and a command line with history on the up and down arrows, completion on Tab, and paging with PageUp and PageDown.
func (g *game) Update(ctx *engine.Context) error {
if ctx.Console.Open() {
return nil // the console has the keyboard
}
...
}
Commands are registered with Register and read their arguments as strings. The built-in commands are help, echo, clear, quit, screenshot, exec, bind, unbind, binds, fps, stats, log, timescale, panels, set, get and vars.
Variables are registered with Float, Int, Bool, String or Var and edited with set and get, so a game exposes its tunables without writing commands for them:
ctx.Console.Float("player.speed", &g.speed, "how fast the player runs")
The panels
F4, or the panels command, opens a resizable window of tabs: Engine (frame timings, profile scopes and draw counts), Graphics (the live post-processing settings and the GPU resources), Entities (the attached worlds, their entities, components, resources and systems), Physics (bodies, contacts, joints, solver settings and collider drawing), Audio (voices, buses and the listener), Input (keys, pointer, gamepads and action maps) and Services (whatever the game attached).
The engine attaches what it owns. A game attaches its own with one call each: Attach for an entity world, AttachActions for an action map, AttachInfo for a line of text in the Services tab and AttachLinks for a network connection's link statistics.
Logging
With Config.Console the engine tees the log through the console, so every slog record shows in the panel as well as wherever it was going. The log command sets the lowest level captured from then on. A console a game builds itself installs the tee with Handler.
Every method works on a nil Console and does nothing, so the calls a game makes stay put when Config.Console is off and the field is nil.
Index
- type Command
- type Console
func New(opts Options) *Consolefunc (c *Console) Attach(name string, w *ecs.World)func (c *Console) AttachActions(name string, a *input.Actions)func (c *Console) AttachInfo(name string, text func() string)func (c *Console) AttachLinks(name string, links func() []Link)func (c *Console) Bool(name string, p *bool, help string)func (c *Console) Clear()func (c *Console) Commands() []Commandfunc (c *Console) Destroy()func (c *Console) Draw(h Host) errorfunc (c *Console) Float(name string, p *float32, help string)func (c *Console) GetVar(name string) (string, bool)func (c *Console) Handler(next slog.Handler) slog.Handlerfunc (c *Console) Int(name string, p *int, help string)func (c *Console) Level() slog.Levelfunc (c *Console) Lines() []stringfunc (c *Console) Open() boolfunc (c *Console) Print(text string)func (c *Console) Printf(format string, args ...any)func (c *Console) Register(name, help string, fn func(args []string) (string, error))func (c *Console) Run(line string)func (c *Console) SetLevel(l slog.Level)func (c *Console) SetOpen(open bool)func (c *Console) SetVar(name, text string) errorfunc (c *Console) String(name string, p *string, help string)func (c *Console) Toggle()func (c *Console) Var(name string, v Var, help string)func (c *Console) VarNames() []stringfunc (c *Console) Worlds() []string
- type Frame
- type Host
- type Link
- type Options
- type Prefabs
- type Scope
- type Stats
- type Var
Types
type Command source
type Command struct {
Name string // command token entered at the prompt
Help string // one-line usage and description
Fn func(args []string) (string, error) // synchronous callback on the Run caller
}
Command is one console command. Fn reads the arguments after the command's name and returns the text to print, which may be empty, or an error, which prints in red.
type Console source
type Console struct {
// contains filtered or unexported fields
}
Console is one debug console: its output, its commands and variables, and the panels. Register commands and variables from any goroutine; draw it from the goroutine that runs the loop. Command callbacks and registered variables are accessed on that loop goroutine. Registration protects the registry, not the pointed-to game values; synchronize any access from other goroutines. Attachments, open state, Run and Draw also belong to the loop goroutine.
New source
func New(opts Options) *Console
New makes a console. Nothing is drawn until Draw is called, and the
font and interface context are made the first time it is.
A console outside the engine's Config.Console: build it, tee the log
through it, and hand it the frame every Draw.
Example
package main
import (
"log/slog"
"github.com/matjam/bunyip/console"
"github.com/matjam/bunyip/input"
)
func main() {
con := console.New(console.Options{Key: input.KeyF1, Height: 0.5})
log := slog.New(con.Handler(slog.Default().Handler()))
log.Info("the console is up")
// Then, last of all in Draw:
// con.Draw(ctx)
}
Attach source
func (c *Console) Attach(name string, w *ecs.World)
Attach adds an entity world to the Entities and Physics panels under a name. Attach several worlds and the panels show a row of them to pick from. Attaching the same name again replaces that world.
AttachActions source
func (c *Console) AttachActions(name string, a *input.Actions)
AttachActions adds an action map to the Input panel, which lists every bound action with its sources and its value this frame.
AttachInfo source
func (c *Console) AttachInfo(name string, text func() string)
AttachInfo adds a line to the Services panel, redrawn from text every frame. It is how a game shows a service the console knows nothing about: the locale, a save slot, a scheduler's timer count.
con.AttachInfo("locale", func() string { return tr.Lang() })
con.AttachInfo("timers", func() string { return strconv.Itoa(sched.Pending()) })
AttachLinks source
func (c *Console) AttachLinks(name string, links func() []Link)
AttachLinks adds a connection's links to the Services panel. The console does not depend on the network package, so the game reads the statistics it wants shown:
con.AttachLinks("server", func() []console.Link {
var out []console.Link
for _, a := range peer.Peers() {
s, _ := peer.Stats(a)
out = append(out, console.Link{Peer: a.String(), RTT: s.RTT,
Loss: s.Loss, Pending: s.Pending, Connected: s.Connected})
}
return out
})
Bool source
func (c *Console) Bool(name string, p *bool, help string)
Bool registers a bool the set command edits; set takes true, false, 1, 0, on or off.
Commands source
func (c *Console) Commands() []Command
Commands returns every registered command, sorted by name.
Destroy source
func (c *Console) Destroy()
Destroy frees the font the console made for itself. The engine calls it before the graphics context goes; a game that passed its own font in Options keeps that font.
Draw source
func (c *Console) Draw(h Host) error
Draw runs one frame of console: the toggle keys, the key bindings, the command line while it is open, and the panels. Config.Console calls this automatically. For a console created with New, call it last in the game's Draw so it sits above everything else. It draws nothing while the console is closed and no panel window is open.
Float source
func (c *Console) Float(name string, p *float32, help string)
Float registers a float32 the set command edits, for a tunable a game wants to try values for while it runs:
con.Float("player.speed", &g.speed, "how fast the player runs")
GetVar source
func (c *Console) GetVar(name string) (string, bool)
GetVar returns a variable's value as text, and false when no variable has that name.
Handler source
func (c *Console) Handler(next slog.Handler) slog.Handler
Handler returns a slog.Handler that passes records on to next and copies those at the console's level and above into its output, so the game's log shows in the console as well as wherever it was going. The engine installs it when Config.Console is set; a game building its own console installs it itself:
con := console.New(console.Options{})
cfg.Log = slog.New(con.Handler(slog.Default().Handler()))
Records are captured as they arrive, so lowering the threshold with the log command shows more from then on and never rewrites what is already there. Handle is safe to call from any goroutine.
Int source
func (c *Console) Int(name string, p *int, help string)
Int registers an int the set command edits.
Level source
func (c *Console) Level() slog.Level
Level is the lowest log level the console captures.
Lines source
func (c *Console) Lines() []string
Lines returns the output kept, oldest first, for tests and for a game that shows the log somewhere of its own.
Open source
func (c *Console) Open() bool
Open reports whether the drop-down is showing. A game checks it in Update and leaves the keyboard alone while it is true. Toggle keys are processed in Draw, so Update observes the state from the preceding Draw. The debug panels alone do not make Open true.
Print source
func (c *Console) Print(text string)
Print adds a line of output. It is safe to call from any goroutine.
Printf source
func (c *Console) Printf(format string, args ...any)
Printf adds a formatted line of output, as fmt.Sprintf writes it.
Register source
func (c *Console) Register(name, help string, fn func(args []string) (string, error))
Register adds a command, replacing any command of the same name. Help is the one-line description the help command prints:
con.Register("give", "give <item> [count]: add an item", func(args []string) (string, error) {
...
})
Example
A game registers its commands, variables and worlds once, from Init.
package main
import (
"fmt"
"github.com/matjam/bunyip/console"
"github.com/matjam/bunyip/ecs"
"github.com/matjam/bunyip/input"
)
// In a game these come from the game's own state; the console itself
// comes from engine.Context.Console once Config.Console is set.
var (
con *console.Console
world *ecs.World
actions *input.Actions
speed float32 = 6
noclip bool
)
func main() {
con.Register("give", "give <item> [count]: add an item to the pack",
func(args []string) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("give: needs an item")
}
return "gave " + args[0], nil
})
con.Float("player.speed", &speed, "how fast the player runs")
con.Bool("player.noclip", &noclip, "walk through walls")
con.Attach("world", world)
con.AttachActions("player", actions)
con.AttachInfo("save slot", func() string { return "slot 2" })
}
Run source
func (c *Console) Run(line string)
Run parses and runs one command line, printing what it returns. An unknown command is an error. Call it from the goroutine that draws.
SetLevel source
func (c *Console) SetLevel(l slog.Level)
SetLevel changes the lowest level captured, as the log command does.
SetVar source
func (c *Console) SetVar(name, text string) error
SetVar parses text into a registered variable.
String source
func (c *Console) String(name string, p *string, help string)
String registers a string the set command edits.
Toggle source
func (c *Console) Toggle()
Toggle flips the drop-down open or shut, as the console's key does.
Var source
func (c *Console) Var(name string, v Var, help string)
Var registers a variable of any type, replacing one of the same name. The console never copies the value: it reads and writes through v whenever the game asks, so a variable stays live.
type Frame source
type Frame struct {
Gfx *gfx.Graphics // drawing context; required by Draw
Input *input.State // frame input; required by Draw
Audio *audio.Mixer // optional mixer for the Audio panel
Log *slog.Logger // host logger, available to console integrations
// Clipboard is what the panels' text fields cut, copy and paste
// through; the engine's Context satisfies it.
Clipboard ui.Clipboard
// Width and Height are the view's size in view units.
Width, Height float32
// Delta is the seconds the last update covered, Time the seconds the
// game has run, and FrameCount the frames drawn.
Delta, Time float64
FrameCount uint64 // number of frames drawn
// Stats are the previous frame's timings and counts.
Stats Stats
// Screenshot writes the next drawn frame to a PNG at the path.
Screenshot func(path string)
// Quit ends the loop.
Quit func()
// SetTimeScale and TimeScale drive the timescale command.
SetTimeScale func(scale float64)
TimeScale func() float64 // read the current game-time multiplier
}
Frame is the engine state one console frame draws from. The engine fills it in from its own context, so a game never builds one; a game driving a console outside the engine's loop fills in what it has. Every field may be zero, and the console leaves out what it is not given: without Screenshot the screenshot command reports an error, and without Audio the audio panel says so. Draw does nothing unless both Gfx and Input are non-nil.
type Host source
type Host interface {
ConsoleFrame() Frame
}
Host is anything that can describe the frame the console draws in. The engine's *engine.Context implements it, so a game passes its context straight to Draw.
type Link source
type Link struct {
Peer string // who the link is to
RTT time.Duration // round trip time
Loss float32 // fraction of packets lost, 0 to 1
Pending int // messages sent and not yet acknowledged
Connected bool // false marks the peer as down in the panel
}
Link is one network link as the Services panel shows it.
type Options source
type Options struct {
// Key opens and closes the drop-down; zero means the backquote key.
Key input.Key
// PanelKey opens and closes the window of debug panels; zero means F4.
PanelKey input.Key
// Height is the fraction of the view the drop-down covers, 0 to 1;
// zero means 0.4.
Height float32
// Lines is how many lines of output are kept; zero means 1000.
Lines int
// Level is the lowest log level captured by Handler; zero means Info.
// The log command changes it while the game runs.
Level slog.Level
// Font draws the console; nil makes one from the built-in font at 13
// view units the first time the console draws.
Font *gfx.Font
// Theme styles the panels; nil uses the dark theme over Font.
Theme *ui.Theme
// Read is where exec reads scripts from: pass an asset.FS's Read
// method to run scripts out of a pack file. Nil reads the file system.
Read func(name string) ([]byte, error)
}
Options configure a console. Every zero value means the default noted.
type Prefabs source
type Prefabs map[string]*ecs.Prefab
Prefabs is a named set of prefabs the Entities panel can spawn. Set it as a resource on a world and the panel lists the names with a button each:
w.SetResource(console.Prefabs{"goblin": goblinPrefab})
type Scope source
type Scope struct {
Name string // profile section or GPU pass name
MS float64 // duration in milliseconds
}
Scope is one timed section of game code, as Context.Profile records it.
type Stats source
type Stats struct {
FPS float64 // frames per second over the last second
FrameMS float64 // wall time from one frame's start to the next
UpdateMS float64 // time spent in Update calls this frame
DrawMS float64 // time spent in Draw
PresentMS float64 // time submitting and waiting on the GPU
Updates int // Update calls this frame
Scopes []Scope // profile scopes recorded this frame
// GPUFrameMS is how long the GPU spent on a recent frame and GPU is
// that time by pass. Both come from timestamp queries read back a
// frame or two later, so they lag the frame on screen, and both are
// zero and empty on a device without timestamp queries.
GPUFrameMS float64
GPU []Scope // GPU duration by pass in milliseconds
// DrawBudget is the draw call count a frame should stay under, from
// Config.DrawBudget; zero means no budget was set.
DrawBudget int
}
Stats are the frame timings and counts the engine panel shows. They mirror engine.Stats, which the engine copies in, so the console does not depend on the root package.
type Var source
type Var interface {
String() string
Set(text string) error
}
Var is a console variable of a type the console has no helper for: a colour, an enumeration, a value behind a lock. String is what get prints and Set parses what set is given.
Source files
attach.go bench_test.go command.go console.go console_test.go entities.go example_test.go frame.go hook_test.go log.go panels.go panels_test.go ring_test.go services.go shot_test.go vars.go