Skip to content

Debug and live

Debug drawing and the debug overlay, and the live link with the editor.

Files: debug.odin, live.odin, live_native.odin (#+build !js), live_web.odin (#+build js).

Debug drawing and the debug overlay, for finding out what a game is doing while it runs.

Debug shapes are lines, rectangles, circles and text in the world, queued from anywhere (an update, a script, a collision handler) and drawn over the scene through its camera, after its nodes and physics shapes. They last one frame unless given seconds, so drawing one every frame shows something live:

Example

e.RayHit
// Where the enemy is looking, and what it saw
e.Debug_Line(scene, enemy.position, enemy.position + facing * 200)
if hit.hit do e.Debug_Circle(scene, hit.point, 6, {255, 60, 60, 255}, seconds = 0.5)

The overlay (Game.debugOverlay, or F10 in development runs: DEV_KEY_DEBUG_OVERLAY) shows the frame rate and frame times over the last second or two, the scene, its node, body and tween counts, and the time scale, in the top-left corner over everything.

The live link: a game run by an editor (the player’s --live) answers it while it plays: what’s in the playing scene, a node’s properties, how long each part of a frame takes. The editor can change properties, and pause the game and step it a frame at a time.

The channel is the game’s standard input and output, one line per message:

  • Commands (the editor writes them to the game’s stdin; a thread reads them and LiveLink_Process runs them at the start of each update):
    • watch: send stats four times a second and the scene’s tree twice a second
    • select <id>: also send that node’s properties with each tree (select alone: none)
    • set <id> <property> <value>: sets a property (Node_WriteProperty)
    • pause, resume, step (one frame of 1/60 s while paused)
  • Replies (lines on stdout starting @@live , then JSON):
    • {"type":"stats","fps":..,"frame":..,"update":..,"physics":..,"draw":..,"nodes":..,"drawn":..,"paused":..} (milliseconds, averaged over the last frames)
    • {"type":"tree","scene":"..","nodes":[{"id":"..","name":"..","type":"..","parent":"..","visible":true}, ...]}
    • {"type":"node","id":"..","properties":[{"name":"..","value":".."}, ...]}
    • {"type":"set","ok":true,"id":"..","property":".."}

Ids are the nodes’ EntityIds as text. Exports never run with --live; on the web there’s no stdin, so LiveLink_Start does nothing there.

DebugShapeKind :: enum u8 {
// A line from `a` to `b`
Line,
// A rectangle: `a` the top-left corner, `b` the size
Rect,
// A circle: `a` the center, `b.x` the radius
Circle,
// Text with its top-left corner at `a`
Text,
}

What a debug shape is

enum. Source: debug.odin:33

DebugShape :: struct {
// What it is
kind: DebugShapeKind,
// Rect, Circle: filled, or only the outline
filled: bool,
// Its color
color: sdl.Color,
// Line: from `a` to `b`; Rect: `a` the top-left corner, `b` the size; Circle: `a` the center,
// `b.x` the radius; Text: `a` the top-left corner
a, b: [2]f32,
// Text: the text (owned, on the heap)
text: string,
// Seconds it has left; it goes at the first update after this reaches 0
remaining: f32,
}

One queued debug shape (Debug_Line…), in world pixels

struct. Source: debug.odin:45

FrameStats :: struct {
// Seconds per frame, a ring of the last DEBUG_FRAME_HISTORY
times: [DEBUG_FRAME_HISTORY]f32,
// Where the next one goes, and how many there are (up to DEBUG_FRAME_HISTORY)
next, count: int,
}

The frame times the debug overlay shows (Game_Update records them, in real time)

struct. Source: debug.odin:62

LiveLink :: struct {
// Lines read from stdin, waiting for the next update (guarded by `lock`)
lock: sync.Mutex,
incoming: [dynamic]string,
// The game's updates are held (it still draws)
paused: bool,
// One update to run while paused
step: bool,
// Stats and the tree are being sent
watching: bool,
// The node whose properties are sent ("": none)
selected: string,
// Seconds to the next stats and tree
statsTimer, treeTimer: f32,
}

A game’s live link (Game.live, made by LiveLink_Start)

struct. Source: live.odin:34

GameProfile :: struct {
// Milliseconds: globals and the scene's update (physics included), physics alone, drawing
update, physics, draw: f32,
// Nodes in the playing scene, and drawn last frame
nodes, drawn: int,
}

How long the parts of the last frames took (Game.profile): measured every frame, averaged over PROFILE_FRAMES.

struct. Source: live.odin:54

Debug_Line :: proc(scene: ^Scene, from, to: [2]f32, color := DEBUG_COLOR, seconds: f32 = 0)

Queues a line in the world, drawn over the scene.

Inputs

  • scene: The scene to draw it over
  • from: Start, in world pixels
  • to: End, in world pixels
  • color: Its color
  • seconds: How long it stays (0: this frame)

Example

e.Debug_Line(scene, {0, 0}, {200, 100}, sdl.Color{255, 60, 60, 255}, seconds = 1)

procedure. Source: debug.odin:82

Debug_Rect :: proc(scene: ^Scene, rect: sdl.FRect, color := DEBUG_COLOR, filled := false, seconds: f32 = 0)

Queues a rectangle in the world, drawn over the scene.

Inputs

  • scene: The scene to draw it over
  • rect: World pixels (x, y the top-left corner)
  • color: Its color
  • filled: Filled, or only its outline
  • seconds: How long it stays (0: this frame)

procedure. Source: debug.odin:96

Debug_Circle :: proc(scene: ^Scene, center: [2]f32, radius: f32, color := DEBUG_COLOR, filled := false, seconds: f32 = 0)

Queues a circle in the world, drawn over the scene.

Inputs

  • scene: The scene to draw it over
  • center: World pixels
  • radius: World pixels
  • color: Its color
  • filled: Filled, or only its outline
  • seconds: How long it stays (0: this frame)

procedure. Source: debug.odin:111

Debug_Text :: proc(scene: ^Scene, position: [2]f32, text: string, color := DEBUG_COLOR, seconds: f32 = 0)

Queues text in the world (the default font, 14 px at zoom 1), drawn over the scene.

Inputs

  • scene: The scene to draw it over
  • position: The text’s top-left corner, world pixels
  • text: What it says (copied)
  • color: Its color
  • seconds: How long it stays (0: this frame)

Example

// Given: enemy: ^e.Node, health: int
e.Debug_Text(scene, enemy.position + {0, -40}, fmt.tprintf("hp %d", health))

procedure. Source: debug.odin:129

Debug_Clear :: proc(scene: ^Scene)

Removes every debug shape a scene has queued, lasting ones too.

Inputs

  • scene: The scene

procedure. Source: debug.odin:139

Game_GetFrameStats :: proc(game: ^Game) -> (fps, average, worst: f32)

The frame rate and frame times over the frames the game recorded (up to DEBUG_FRAME_HISTORY), in real time (not scaled by the time scale).

Inputs

  • game: The game

Returns

  • fps: Frames per second, from the average frame time (0 before the first frame)
  • average: The average frame time, milliseconds
  • worst: The longest frame, milliseconds

Example

fps, average, worst := e.Game_GetFrameStats(game)
log.infof("%.0f fps, %.1f ms (worst %.1f)", fps, average, worst)

procedure. Source: debug.odin:160

LiveLink_Process :: proc(game: ^Game, deltaTime: f64) -> bool

Runs the commands that came in since the last update, and sends what the editor watches. Game_Update calls it first.

Inputs

  • game: The game
  • deltaTime: Seconds since the last frame

Returns

  • false while the game is paused (no update this frame); true to update (a step included)

procedure. Source: live.odin:78

LiveLink_Push :: proc(game: ^Game, line: string)

Hands the live link a line, as if read from stdin (tests; the reader thread does it in games).

Inputs

  • game: The game (with a live link)
  • line: A command

procedure. Source: live.odin:118

LiveLink_Destroy :: proc(game: ^Game)

Frees a game’s live link (Game_Destroy).

procedure. Source: live.odin:129

Scene_GetNodeByText :: proc(scene: ^Scene, id: string) -> ^Node

A node by its id as text (as the live link sends them).

Inputs

  • scene: The scene
  • id: The id

Returns

  • The node, or nil

procedure. Source: live.odin:151

LiveLink_Start :: proc(game: ^Game)

Starts a game’s live link (live.odin): a thread reads commands from stdin, one per line, for LiveLink_Process to run. The player starts it with --live (the editor’s Play).

Inputs

  • game: The game

procedure, only in builds with #+build !js. Source: live_native.odin:16

LiveLink_Start :: proc(game: ^Game)

The web has no stdin: no live link (live.odin)

procedure, only in builds with #+build js. Source: live_web.odin:5

DEBUG_COLOR :: sdl.Color{80, 255, 120, 255}

The color debug shapes get when none is given

constant. Source: debug.odin:27

DEBUG_FRAME_HISTORY :: 120

How many frame times the overlay’s numbers cover

constant. Source: debug.odin:30

PROFILE_FRAMES :: 30

How many frames the profile averages

constant. Source: live.odin:62

LIVE_PREFIX :: "@@live "

The prefix of the live link’s replies on stdout

constant. Source: live.odin:65