Guide
Windows and async work #
knots.App owns the windows, the event loop, the renderer, and a queue for
async work. This page describes its API.
App configuration #
var app = try knots.App.init(io, gpa, .{
.window = .{
.width = 1280,
.height = 720,
.title = "My app",
.resizable = true,
.min_size = .{ .width = 480, .height = 320 },
.canvas_selector = "#canvas", // Browser builds only.
},
.renderer = .{ .present_mode = .fifo, .clear_color = .{ 0, 0, 0, 1 } },
.ui = .{ .theme = ui.Theme.dark },
.depth_buffer = false,
.accessibility = true,
});
| Field | Description |
|---|---|
window |
Size, title, resizable, min_size,
max_size, and the browser canvas_selector.
|
renderer |
present_mode (default .fifo, which waits for vertical
sync) and clear_color.
|
ui |
theme, fonts, state_ttls, and
scroll_line_size.
|
depth_buffer |
Adds a depth buffer, for GPUCanvas code that needs one. |
arena_reset_mode |
How the frame arena resets. Default: keep the capacity. |
max_completions_recv |
The maximum async completions to run in one frame. Default: 64. |
accessibility |
Turns the platform accessibility adapter on or off. Default: on. |
io runs async work and the window backend. allocator holds
the state that stays for the life of the app. Keep the App at one address
until start returns, because windows keep pointers to it.
The View #
Each frame callback gets a *knots.View and a *ui.Frame:
fn frame(view: *knots.View, context: *ui.Frame) !void { ... }
view.appis the*App. Use it for the calls on this page.-
view.idis theViewport.Idof the window..mainis the first window. -
view.rendereris a snapshot of the renderer: its configuration, the present modes that the surface supports, and the last reconfigure error.
To get your own state, put App in a field of your struct and use
@fieldParentPtr. The tutorial
shows this.
Multiple windows #
Open more native windows after start is called. Each window has its own
frame callback, UI state, and renderer. All windows share one GPU device.
const inspector = try view.app.openWindow(view.id, .{
.window = .{ .width = 520, .height = 320, .title = "Inspector" },
}, inspectorFrame);
// Later:
try view.app.closeWindow(inspector);
-
The first argument is the window to copy the renderer and UI configuration from. Set
rendereroruiin the options to use a different configuration. - Closing the main window closes all windows and stops the app.
- The browser has one canvas, so
openWindowreturnserror.UnsupportedPlatform.
Async work #
A frame callback must return quickly. For slow work, such as file access or network
requests, use dispatch. Knots runs the function concurrently with
io. When it completes, Knots calls your completion callback on the main
thread, inside a frame of the window that you name.
try view.app.dispatch(view.id, loadFile, .{ io, path }, onLoaded);
fn loadFile(io: std.Io, path: []const u8) anyerror![]u8 {
// Runs concurrently. Do not touch UI state here.
...
}
fn onLoaded(view: *knots.View, frame: *ui.Frame, result: anyerror![]u8) !void {
const self: *Self = @alignCast(@fieldParentPtr("app", view.app));
self.contents = try result;
frame.requestRedraw();
}
- The type of the third argument of the callback is the return type of the function.
- If the window closes before the work completes, Knots discards the result.
-
A browser build without worker threads returns
error.ConcurrencyUnavailable. Handle this error. -
app.concurrencyInFlight()returns the number of tasks that have not completed.
Wake the app from other code #
App sleeps when there are no events. If a different thread or a callback
changes your data, call requestFrame:
try app.requestFrame(.main);
In a frame callback, use frame.requestRedraw() instead.
Change the renderer #
reconfigureRenderer changes the present mode or clear color. The change
applies at the next frame. If the surface does not support the present mode,
view.renderer.reconfigure_error holds
error.UnsupportedPresentMode.
var config = view.renderer.config;
config.present_mode = .mailbox;
try view.app.reconfigureRenderer(view.id, config);
Screenshots #
requestReadback copies the next rendered frame of a window to memory.
takeReadback returns the pixels when the copy is complete. The caller then
owns the memory.
try app.requestReadback(.main, gpa);
// In a later frame:
if (try app.takeReadback(.main)) |taken| {
var readback = taken;
defer readback.deinit();
// readback.width, .height, .format, .bytes_per_row, and .bytes hold the image.
try savePixels(readback);
}
Developer tools #
knots.debug.DevTools is an overlay that shows frame time, window size,
async tasks, and renderer settings. Render it at the end of your frame callback. The
playground
example shows how to connect it.