tiny.game.dev
Defined in tiny.game.
A parser for the commands that drive a headless game: advance some steps, report status, or quit.
API (4)
Actions
Public operations.
common: Maps a command name and a step count to aCommand, or returnsnullfor a name other thanstep,statusorquit.parse: Parses one line holding a JSON object into aCommand.validateStepCount: Checks that a step count lies between 1 andmax_steps.
Types and contracts
Public types and contracts.
Command: One of the three commands every headless host understands.
Source
Source: fun/game/src/dev.zig
zig
//! A parser for the commands that drive a headless game: advance some steps, report status, or//! quit.//!//! A tool or test drives the game one command at a time and waits as long as it likes between//! commands, so the game advances only when a command asks it to. A host keeps commands of its own//! beside the shared ones.//!//! A line from an outside tool can name any command and any count, so the host bounds the step//! count and separates the commands it knows from all others.//!//! Each command is one JSON object on its own line, with its name in a `cmd` field and, for a step//! command, a step count in a `frames` field that is 1 when the line leaves it out. The `frames`//! field counts simulation steps. `common` maps a name and a count to a `Command`, and it returns//! `null` for a name outside the three, so a host can try its own commands next. `parse` reads a//! whole line and returns `error.UnknownCommand` for a name outside the three. A step count runs//! from 1 to a maximum the caller passes, and both functions return `error.StepCountOutOfRange` for//! any other count.const std = @import("std");/// One of the three commands every headless host understands. A host switches on it to decide/// whether to advance, report or stop.pub const Command = union(enum) { /// Advance the simulation by the given number of steps, from 1 to the caller's maximum. step: u32, /// Report the host's state. A host leaves the simulation at the step it had reached for this /// command. status, /// Stop serving commands. quit,};/// Checks that a step count lies between 1 and `max_steps`. `common` checks the count of each step/// command with it. The call returns `error.StepCountOutOfRange` for a count of zero or above/// `max_steps`, so a `max_steps` of zero refuses every count. `frames` is a count of simulation/// steps.pub fn validateStepCount(frames: u32, max_steps: u32) error{StepCountOutOfRange}!void { if (frames == 0 or frames > max_steps) return error.StepCountOutOfRange;}/// Maps a command name and a step count to a `Command`, or returns `null` for a name other than/// `step`, `status` or `quit`. A host that has commands of its own calls it first and handles a/// `null` result with its own names. The call checks the count for `step` alone, and returns/// `error.StepCountOutOfRange` for a count of zero or above `max_steps`. Names match byte for byte,/// case included.pub fn common(name: []const u8, frames: u32, max_steps: u32) error{StepCountOutOfRange}!?Command { if (std.mem.eql(u8, name, "step")) { try validateStepCount(frames, max_steps); return .{ .step = frames }; } if (std.mem.eql(u8, name, "status")) return .status; if (std.mem.eql(u8, name, "quit")) return .quit; return null;}const Raw = struct { cmd: []const u8, frames: u32 = 1 };/// Parses one line holding a JSON object into a `Command`. A host that understands only the three/// shared commands parses each line with it. The object names its command in `cmd` and its step/// count in `frames`, which is 1 when the line leaves it out, and the parser skips any other field./// The call returns `error.UnknownCommand` for a name outside the three, checks the count for/// `step` alone, and returns `error.StepCountOutOfRange` for a count of zero or above `max_steps`./// The call returns the errors of `std.json.parseFromSliceLeaky` for a line that is malformed JSON,/// a line whose JSON value is an array, a string, a number, a Boolean or null, a line that repeats/// a field, a line that lacks `cmd`, or a line that holds a field of the wrong type, such as a/// `cmd` that is a number or a `frames` that is negative, fractional or above the `u32` range. A/// `frames` written as a string of digits, such as `"3"`, parses as that number. The parsed strings/// live in `arena`, and the call frees nothing, so the caller resets or frees the arena.pub fn parse(arena: std.mem.Allocator, line: []const u8, max_steps: u32) !Command { const raw = try std.json.parseFromSliceLeaky(Raw, arena, line, .{ .ignore_unknown_fields = true }); return (try common(raw.cmd, raw.frames, max_steps)) orelse return error.UnknownCommand;}test "headless protocol advances only on step commands" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); var clock = try @import("clock.zig").Clock.init(.{ .steps_per_second = 60, .source_frames_per_second = 20 }); for ([_][]const u8{ "{\"cmd\":\"status\"}", "{\"cmd\":\"step\",\"frames\":3}", "{\"cmd\":\"status\"}" }) |line| { const command = try parse(arena, line, 8); switch (command) { .step => |count| { for (0..count) |_| _ = clock.commandStep(); }, else => {}, } } try std.testing.expectEqual(@as(u64, 3), clock.index);}Source: fun/game/src/root.zig:118
zig
pub const dev = @import("dev.zig");Audit
| Definitions | 5 |
|---|---|
| Public names | 5 |
| Members | 3 |
| Version | 26.7.0 |
| Revision | daab053ee433 |