Skip to main content

Crate shuyi_app

Crate shuyi_app 

Source
Expand description

Shuyi Engine’s macroquad backend (ADR-0008 §2, game ADR-0004): the window, blitting the frame (render), the keyboard (keys) and controllers (pads), the audio player and storage (storage), behind shuyi_platform’s Platform, and the loop (run) that calls shuyi_platform::App::frame once per frame of macroquad’s, on desktop, the web and the Steam Deck.

On desktop, read_folder reads a folder on disk (a player’s mod) into a bundle layer the game lays over its own files. The web has no folders, so it isn’t there: there page::page_layer reads the files the page holds into one instead. In a build with debug tools the folders it reads are watched, and watched_layer gives the one cargo shuyi run --watch names (ADR-0071).

A game on the short path (ADR-0036) runs from a plain main, with run_game and its BasicGame:

ⓘ
fn main() {
    shuyi_app::run_game(game::game());
}

A game of its own shape keeps macroquad’s entry point, which has to be in the binary, and hands over its AppConfig and how it starts (Start):

ⓘ
fn window_conf() -> macroquad::window::Conf {
    shuyi_app::window_conf(&WINDOW)
}

fn look(game: &MyGame) -> Look<'_> {
    Look::new(&game.font, &game.images)
}

#[macroquad::main(window_conf)]
async fn main() {
    let start = Start::new(Card::text("My Game"), look, |setup| {
        let ctx = setup.ctx(my_ctx()?);
        Ok(Game::new(ctx, Box::new(Title), GameHooks::default()))
    });
    shuyi_app::run(config(), start).await;
}

With the link feature, a debug build listens for the live link (ADR-0047), so shuyi attach reaches the game running in its window: run it with SHUYI_LINK=0 (a free port; or a port of your choosing), and its log says shuyi link: listening on 127.0.0.1:<port>, token <token>. SHUYI_LINK_TOKEN sets the token instead of a random one. A release build, or one without the feature, never listens; on the web the feature does nothing yet.

With the capture feature, a desktop build runs a plan instead of the player’s input when SHUYI_CAPTURE names one, compares the window with the raster on the frames it names, and exits (the capture module, ADR-0052):

SHUYI_CAPTURE=examples/hello-grid/capture/frames.ron cargo run -p hello-grid --features capture

It writes each capture’s pictures and capture.txt under SHUYI_CAPTURE_OUT (target/capture unless set), and exits 0 when every capture is within the plan’s tolerance, 1 when one isn’t, and 2 when the plan can’t run. Without the feature the variable is never read. On the web a build with the feature always runs the plan capture.ron beside the page, writes no files and logs each capture’s line to the browser’s console instead (ticket 2019).

With the sound-probe feature, SoundProbeBackend names the sound backend, for the example sound_probe that the parity gates record. No game turns it on.

With the in-house-sound feature, sound goes through shuyi-sound’s mixer and shuyi-sound-out instead of quad-snd.

A desktop debug build writes its frames’ times as it runs when SHUYI_PERF names a file (SHUYI_PERF=perf.jsonl cargo run): its log names the file, and after each frame it writes that frame’s record as a JSON line (shuyi_ui::perf::PerfLine) and flushes, so tail -f, or reading the file again from the last line read, follows it (ADR-0046). A build without debug tools logs why and writes nothing; a file that can’t be written is one warning, and the game goes on.

A panic writes a crash report through the game’s storage, beside its saves (the crash module, ADR-0079): crash-<when>.ron in its data folder on native, and in localStorage on the web. At its next start the game hears of it once (shuyi_platform::Setup::last_crash). AppConfig::crash names the game’s version in it, or turns it off.

A desktop debug build keeps its last sessions in sessions/ under the game’s data folder (the sessions module, ADR-0074): the record of a run, written when the game quits and when the debug menu’s “Save session” asks, the newest ten kept. On the web that entry offers a download. shuyi run --session <file> plays one.

Every debug build counts the heap: this crate turns on shuyi_alloc_count’s global feature, whose #[global_allocator] counts the bytes the whole program asks for, every thread’s, and each frame’s record gets the live bytes, their peak and that frame’s allocations (shuyi_ui::perf::FrameRecord::heap). Its counts are also kept by memory tag: the frame’s phases (load, update, draw, paint, perf and app), the game’s spans, and each asset kind’s decode (shuyi_alloc_count::tags). A release build has no counter. A game with a #[global_allocator] of its own (a binary has one) opts out in its .cargo/config.toml:

[build]
rustflags = ["--cfg", "shuyi_no_heap_count"]

On the web the game’s binary must link with --allow-undefined: the JS plugins’ functions (crates/cli/web/gamepad.js’s, download.js’s, page-files.js’s, quad-storage’s) are imports the page provides, not link errors. A link argument only reaches the binary from its own build script (crates/cli/web/README.md shows how).

Re-exports§

pub use crate::crash::CrashReports;

Modules§

crash
Crash reports (ADR-0079): a panic hook that writes a shuyi_platform::crash::CrashReport through the game’s storage, which run installs before its first frame (AppConfig::crash).
keys
Translation from macroquad key codes to shuyi-ui’s hardware-agnostic Key, collecting a frame’s keyboard events as RawInputEvents, and hearing what the window’s focus is made from (WindowHeard).
pads
Controllers (ticket 0219, game ADR-0034): reads every connected pad’s raw state each frame, with gilrs on native and the browser’s Gamepad API (through crates/cli/web/gamepad.js) on web, and hands it to shuyi-ui’s Pads, which holds all the rules (sticks as directions, the Switch-style swap, several pads as one). Like keys.rs, this only translates, and ignores the pads while the window has no focus (pad_events).
page
Files from the page, on the web: the text files the page a web build runs in holds, read into a bundle layer (ADR-0004 §1) the game lays over its own files, and the game’s problems with them sent back (report). It is how a page that edits a game’s data beside it (the site’s playground) runs the game on its edits with no install. On every other target the page holds none.
render
Blits a GlyphBuffer to the window with the font atlas (game ADR-0003): integer-scaled, centred, black letterbox. The buffer’s items are drawn scaled, in the order they were added: Under ones after the cell backgrounds, Over ones after the glyphs. A rectangle (game ADR-0018) is a solid fill; a sprite (game ADR-0038) is part of an image’s texture, or of a recoloured copy of it when the sprite isn’t painted in the image’s own colours (game ADR-0049). A buffer’s backdrop (game ADR-0048), a second buffer panned and zoomed, is drawn the same way inside its window, behind the console. A wide glyph (its continuation cell after it) is one quad over both its cells, as the raster paints it. A painted picture (ADR-0078) is a texture of its own, uploaded only in a frame whose revision moved (PaintedTextures, in painted.rs), drawn with nearest-pixel sampling.
storage
Platform Storage backends (ticket 0207, game ADR-0009): files in the OS data directory on native, localStorage on web.

Structs§

AppConfig
What a game tells the macroquad backend. How it starts, loads and is painted is its Start. Build it with AppConfig::new.
Icon
A window icon: PNG files of 16, 32 and 64 pixels square. It is shuyi_platform’s, which a game’s AppInfo carries (ADR-0061). A window’s icon: PNG files of 16, 32 and 64 pixels square, which shuyi app-info downscales from the game’s one icon (ADR-0061). Build it with WindowIcon::new.
VoiceFiles
Where a game’s voice clips are (game ADR-0046): a folder shipped beside the game (not embedded), with a folder per language holding the manifest and the clips.
Window
The window a game opens. Build it with Window::new: a struct literal doesn’t compile outside this crate, so a new field never breaks a game (ADR-0039).

Enums§

FolderError
Why a folder could not be read into a layer (read_folder).

Functions§

read_folder
Every file in the folder at path and the folders below it, as one layer named name, each at its path relative to the folder, /-separated (data/crops.ron). A link to a file is read; a link to a folder is not followed. Read it once: the layer’s files are kept for the life of the program.
read_voice_manifest
The text of the voice manifest file in dir, or None if there is none (no voices: not a problem). Err if the file is there and can’t be read.
run
Runs a game until it quits, as start says: the first frame shows its loading card, the next loads it (or shows why it can’t: a wrong command line, a broken asset), and each frame after, App reads the keyboard and controllers, steps the game, plays its sounds, music and voices and draws its frame. Closing the window lets the game save first.
run_game
Runs a game on the short path until it quits, in a window of its console at twice its size: its files become the process’s bundle, and run starts it as BasicGame::start says, with no sounds, no voices and no system keyboard. Called from a plain fn main. Under cargo shuyi run --watch the game’s files on disk are laid over the embedded ones (watched_layer).
watched_layer
The folder cargo shuyi run --watch named (WATCH_VAR) as a layer to lay over the game’s files, watched like any folder read_folder reads and shown under the folder’s name: in a build with debug tools, when it is set. A folder that can’t be read logs why, and the game runs on its embedded files.
window_conf
The window settings for macroquad’s entry point (#[macroquad::main(window_conf)]).