Context Pattern
May 31, 2026 · View on GitHub
Yetty avoids threading many arguments through factory functions. Instead of:
/* NO — long, fragile, hard to extend */
thing_create(device, queue, allocator, config, event_loop, pty_factory, ...);
each level of the system bundles what its children need into a small context struct and passes that by pointer:
/* YES — one borrowed context */
struct yetty_yterm_terminal_result yetty_yterm_terminal_create(
struct yetty_ycore_grid_size grid_size, const struct yetty_context *yetty_context);
Contexts are plain C structs (POD). They hold copies of the parent slice and borrowed pointers to objects the parent owns. There is no global state — a component reaches everything it needs through the context it was handed.
See Design Overview for where this sits among the other core decisions.
The build chain
Three layers bring a process up, in strict order. Each is a separate module so that non-terminal apps (analyzers, diagnostics, visualizers) can reuse the lower two and supply only their own body.
yinit ──────────► yframework ──────────► yetty
(platform bootstrap) (GPU/event/RPC services) (terminal app)
build: yinit → yframework → app
teardown: app → yframework → yinit (strictly nested)
- yinit (
include/yetty/yinit/yinit.h) — paths, asset extraction, config parsing, window + WebGPU surface, event pipes, OS event loop. It calls a worker function on a dedicated thread and hands it ayetty_yinit_runtime. - yframework (
include/yetty/yframework/yframework.h) — requests the WebGPU adapter/device/queue, builds the GPU allocator, MSDF generator, render target, event loop, and the optional VNC and RPC servers. - yetty (
include/yetty/yetty/yetty.h) — the terminal application: tabs, panes, terminals.
The real entry point (src/yetty/ymain/glfw.c) is a thin wrapper:
int main(int argc, char **argv)
{
struct yetty_yinit_app_config cfg = { .extract_assets_fn = yetty_platform_extract_assets };
return yetty_yinit_run(argc, argv, &cfg, yetty_worker, NULL);
}
/* worker runs on the render thread once the platform is up */
static struct yetty_ycore_void_result yetty_worker(struct yetty_yinit_runtime *rt, void *user)
{
struct yetty_yplatform_pty_factory *pty_factory = /* ...create from rt->config... */;
struct yetty_yframework *yframework = yetty_yframework_create(rt).value;
struct yetty_yetty_yetty *yetty = yetty_create(yframework, pty_factory).value;
struct yetty_ycore_void_result res = yetty_run(yetty);
/* teardown is the exact reverse: yetty → yframework → pty_factory */
yetty_destroy(yetty);
yetty_yframework_destroy(yframework);
pty_factory->ops->destroy(pty_factory);
return res;
}
The context structs
yetty_yinit_runtime — platform slice (owned by yinit)
Everything the platform produced before the app runs. Borrowed by the worker; yinit frees it after the worker returns.
struct yetty_yinit_runtime {
int argc; char **argv; /* CLI passthrough (NULL on android) */
struct yetty_yconfig_config *config; /* parsed config (owned by yinit) */
void *instance; /* WGPUInstance */
void *surface; /* WGPUSurface — NULL in headless mode */
uint32_t surface_width, surface_height;
float content_scale; /* framebuffer/window (HiDPI) */
void *x11_display; unsigned long x11_window; /* X11 only; NULL/0 elsewhere */
void *window; /* opaque native window handle */
struct yetty_ycore_xthread_event_pipe *platform_input_pipe; /* main → worker */
struct yetty_ycore_xthread_event_pipe *output_pipe; /* worker → main */
struct yetty_platform_clipboard_manager *clipboard_manager;
struct yetty_yplatform_window_manager *window_manager;
};
surface == NULL is the headless case (vnc/headless=true in config): the
worker still runs, just without a presentable surface.
yetty_yinit_gpu_context — platform GPU slice
The GPU-relevant subset of the above, embedded by value so a consumer can pass a pointer to just this slice.
struct yetty_yinit_gpu_context {
WGPUInstance instance;
WGPUSurface surface;
uint32_t surface_width, surface_height;
float content_scale;
void *x11_display; unsigned long x11_window;
};
yetty_yframework_gpu_context — runtime GPU objects
Built on top of the platform slice. Lives on struct yetty_yframework.
struct yetty_yframework_gpu_context {
struct yetty_yinit_gpu_context app_gpu_context; /* copy of the platform slice */
WGPUAdapter adapter;
WGPUDevice device;
WGPUQueue queue;
WGPUTextureFormat surface_format;
struct yetty_ydraw_gpu_allocator *allocator;
struct yetty_ymsdf_generator *msdf_generator; /* cpu | gpu, from config */
};
yetty_context — what the terminal hierarchy receives
The compact context propagated down through tabs, panes, and terminals.
struct yetty_context {
struct yetty_yframework *runtime; /* source of truth for GPU/ */
/* event/RPC/render-target */
struct yetty_yplatform_pty_factory *pty_factory; /* yetty-specific */
struct yetty_yevent_event_loop *event_loop; /* alias of runtime->event_loop */
};
runtime is the one borrowed object that owns the WebGPU device, queue,
allocator, MSDF generator, render target, and the optional VNC/RPC servers.
event_loop is a convenience alias so hot paths don't dig through runtime
each time.
yetty_yterm_terminal_context — terminal leaf
The terminal adds only its PTY to the context it received.
struct yetty_yterm_terminal_context {
struct yetty_context yetty_context; /* copy of parent */
struct yetty_platform_pty *pty;
};
Ownership rules
- A context holds a copy of its parent's context (cheap, POD).
- A context holds borrowed pointers to objects owned by a specific level; the level that creates an object destroys it.
- Lifetimes are strictly nested: a child must be torn down before the parent
whose context it borrowed. The
ymainworker enforces this explicitly (yetty_destroybeforeyetty_yframework_destroy) because pending GPU readback callbacks dereference the framework's render target and event loop.
Pointers
- Bootstrap:
include/yetty/yinit/yinit.h,src/yetty/ymain/glfw.c - Services:
include/yetty/yframework/yframework.h - App + context:
include/yetty/yetty/yetty.h - Terminal leaf:
include/yetty/yterm/terminal.h