Resources and ownership

A resource is a native object that holds data used by rendering, such as a text buffer, a cell buffer, or an image. These objects can stay alive across many frames. Creating one is not the same as drawing it, and finishing a frame does not release it.

Each resource needs a lifetime: keep it alive while your program uses it, then release it when that work ends. Releasing it too early makes further access invalid. Keeping unused resources alive holds memory that the program no longer needs.

Ownership defines who keeps a resource alive and who releases it. It also determines which resources can work together and what happens when you destroy a larger part of the interface.

Group resources in a Context#

A Context is the native resource owner. Create it with ot_context_create before you create buffers, images, or scene nodes. It keeps related resources together and gives each one an identity that native calls can check. When ot_context_destroy succeeds, the Context releases its remaining resources.

A Context does not require a terminal connection. For an offscreen drawing tool, a Context and a cell buffer can be enough. Keep the Context alive until all work that uses its resources finishes.

Add a Session for output#

A Session owns an ordered output queue and terminal state within a Context. Use separate Sessions for independent output streams. They can share a Context’s resources without sharing frame or output state.

ot_session_create does not create a renderer or set terminal modes. Call ot_session_attach_renderer when the Session needs a drawing area, cell buffers, and encoding state. Terminal setup is a separate operation, ot_session_setup_terminal.

A scene is the node tree used for layout and painting in that Session. Creating a scene node requires an attached renderer. Create the root node first. That creates the scene. Before then, other node kinds return OT_INVALID_PHASE. A scene has at most one root at a time.

The ownership structure is:

Keep ownership separate from the scene tree#

Consider a box with a text label as its child. That parent-child relationship controls layout and drawing, not the child’s lifetime. Detaching the label removes it from the box, but leaves the label’s native object alive. Destroying the box also detaches a surviving label rather than destroying it.

The native operations make those distinctions explicit:

Operation Effect
ot_scene_move_node with a NULL parent Detach the label. Its native object still exists
ot_scene_destroy_node on the box Destroy the box and detach its surviving children
ot_scene_destroy_node on the label Destroy the label and its internally owned text resources
ot_session_destroy Destroy the Session and all its scene nodes, including detached nodes
ot_context_destroy Release the Context’s remaining resources if no work keeps it busy

Reparent a node within its scene when you want a different layout relationship. To move content into another scene, create new nodes there. Keep application state outside the native nodes if it must survive that move.

A handle names an object#

Most C creation calls return an ot_handle:

typedef struct ot_handle {
    uint64_t context_id;
    uint32_t slot;
    uint32_t generation;
} ot_handle;

The Context uses the slot to find an object and the generation to distinguish it from an object that later reuses that slot. After you destroy the label, a copied label handle is stale. Creating another label does not make that old handle valid again.

Copying a handle does not keep its object alive. Checked calls detect misuse. A handle from another Context returns OT_WRONG_CONTEXT. A handle of the wrong kind returns OT_WRONG_KIND. A handle to a destroyed object returns OT_STALE_HANDLE. Handles also belong to one loaded native library instance. Do not transfer them between Contexts or library loads.

The ot_context * pointer is not a checked resource handle. Successful ot_context_destroy invalidates every copy of that pointer. Never call native code with it afterward. The scene has no separate C resource handle. Scene-wide calls use its Session’s handle.

Create shared resources in one Context#

A text node (OT_SCENE_TEXT) owns its own text buffer and view. That is enough when content belongs to one node.

For content that outlives a node, create a text buffer explicitly, then create a view of that buffer. The buffer holds text. The view holds viewport and drawing state. Bind the view to an OT_SCENE_TEXT_VIEW node with ot_scene_set_text_view.

These are native creation entry points. Each C call takes the destination Context, and each Zig method belongs to that Context:

Resource C Zig Context method
Cell buffer ot_buffer_create createBuffer
Text buffer ot_text_buffer_create createTextBuffer
Text view ot_text_buffer_view_create createTextBufferView
Edit buffer ot_edit_buffer_create createEditBuffer
Editor view ot_editor_view_create createEditorView
Syntax style ot_syntax_style_create createSyntaxStyle

A document is a text buffer or an edit buffer. Bindings between resources have different retention and destruction rules:

Binding Retention and destruction Sharing and layout
Document → text/editor views Document destruction destroys every dependent view. View destruction leaves the document alive. One document can supply separate views in several Sessions within its Context.
Node → text/editor view The binding does not transfer ownership. Destroying either side removes the binding. View destruction invalidates the node’s measurement and preparation state. One view binds to at most one node. The node supplies the view’s layout dimensions.
Node → image/offscreen buffer The node keeps the storage alive after you destroy the public handle. Replacement retains the new resource before releasing the old one. Node destruction releases it. Compatible nodes can share the resource across Sessions within its Context.
Document → syntax style The document does not own the public style handle. Style destruction clears the document’s binding. Documents in one Context can share a style. Style IDs belong to that style.
Box → viewport node The Box copies the handle without retaining the node. Viewport destruction makes later preparation fail until you replace or disable the binding. The viewport must be a root or Box node in the same scene.

Use separate views for placements with independent dimensions. Tree membership, resource ownership, and resource retention are separate relationships.

Related resources must belong to the same Context. For example, cell data can contain references to the Context’s grapheme and hyperlink pools. Copying raw identifiers into another Context would not copy the referenced content. For a shared theme, reuse the theme definitions but create a style resource in each destination Context.

Read text and geometry#

Documents own text. Views own wrapping, viewport, and selection state. Scene nodes place and measure views. An ordinary TEXT node owns a private document/view pair. A TEXT_VIEW or EDITOR node binds a public view.

Text, range, and selected-text copies use UTF-8 byte counts:

  1. Call the query with zero capacity to get the exact count, without a terminating NUL.
  2. Supply a destination with at least that capacity to copy the bytes.

If a nonzero destination is too short, the call returns an error and changes neither the bytes nor the output count. Empty, reversed, and out-of-document ranges return zero. Record queries count records. Output and diagnostic queues have separate rules for drains.

Range offsets use display cells, with one position for each line separator. Range columns also use display cells. The exclusive end is clamped to the document length. Range starts snap backward to the containing grapheme. Exclusive ends snap forward to include a partially selected grapheme. Extraction uses the document’s selected width method. Text metadata distinguishes UTF-8 bytes from summed line widths, which exclude separators.

Selected-text reads do not prepare virtual lines, follow the cursor, or change the viewport. Coordinate-based edit ranges can prepare the document’s marker cache. View line queries can prepare wrapping. Editor info queries follow the cursor only when you request that behavior.

One ot_styled_text_chunk record supplies scene text, shared replacements, replacement batches, and editor placeholders. The record can identify URL bytes. ot_scene_set_styled_text accepts those bytes through the same operation. Placeholders reject links, including a link flag with an empty URL span. Plain-text replacement keeps its separate UTF-8 operation.

ot_scene_get_layout uses named observation modes:

Mode Observation
OT_LAYOUT_PUBLIC Local cell geometry from the latest preparation refresh. Screen coordinates use current accepted ancestors and translations.
OT_LAYOUT_YOGA The six computed Yoga values, including zero dimensions. Screen coordinates are zero.
OT_LAYOUT_PAINT Prepared paint geometry, including screen coordinates. During a RECORD request, mutations do not change these values.

These queries do not run Yoga. Other mode values return OT_INVALID_ARGUMENT and leave the output unchanged. A layout result is an observation only. It cannot replace a frame ticket. The checked header defines record layouts, named selectors, color construction, bounds, and failure results. Use the header and native library from the same revision.

Create images in a Context#

ot_image_inspect, ot_image_decode, and ot_image_create_pixels use the Context’s allocator, limits, and handle identities. Creation copies input bytes. Encoded input has a 64 MiB limit. Dimensions have a 16,384-pixel limit per axis and a 25,000,000-pixel limit in total.

The checked image operations also support pixel updates, metadata, transformations, composition, and pixel or PNG copies. ot_image_retain shares immutable storage within one Context. ot_image_clone can copy pixels and encoded data into another Context. An exclusive pixel update assigns a new identity to the render cache.

ot_image_take_pixels consumes an exclusive image handle and returns a mutable RGBA8 lease. The lease uses the Context’s lease-count and byte limits. It blocks Context destruction until ot_image_pixels_release succeeds. Release invalidates all saved aliases. PNG copies use the exact-count and short-destination rules for text copies.

Compose an owned buffer#

An offscreen buffer stores a grid of cells independently of the scene. You can draw into it, keep the result, and later compose those cells into a frame.

To display an owned buffer in a scene, bind it to an OT_SCENE_CUSTOM node with ot_scene_set_surface. That binding retains the buffer for composition. The C interface has no separate general-purpose Surface handle.

To copy a buffer directly into a Session’s next framebuffer, use ot_session_draw_buffer while no scene frame is in progress, waiting for commit, or waiting for output delivery. After a frame step returns DONE, use ot_scene_frame_draw_buffer with that DONE ticket instead.

The Zig guide includes a complete offscreen-buffer test.

Borrow cells for one synchronous operation#

To read native cell arrays without copying them, acquire a storage lease. The lease keeps the allocation alive until you release it. Keep the access in one synchronous scope and release the lease on every exit path. In C, ot_buffer_acquire_lease acquires a lease on an owned buffer, and ot_buffer_lease_release releases it.

A lease is not a frozen copy of the cells. Drawing can change them. Resize or destruction can make the allocation stale even while a lease keeps it allocated. Validate with ot_buffer_lease_validate before further access after an operation that can replace storage.

The C lease snapshot exposes these fields:

Field Meaning
width, height Dimensions in terminal cells
generation Identity of this storage allocation
char_ptr One uint32_t entry per cell
fg_ptr, bg_ptr Four uint16_t color entries per cell
attributes_ptr One uint32_t entry per cell

Addresses use uint64_t fields. Convert them through uintptr_t before making C pointers. Keep the Context and native library loaded until you release every lease. Saved pointers are invalid after release. Copy data into your own storage if it must survive the borrow.

Cell storage can contain pooled graphemes, continuation cells, hyperlinks, and image references, not just character codes and colors. Use drawing methods to change text. Raw writes cannot maintain those references. To copy resolved UTF-8, use ot_buffer_lease_get_real_char_size and ot_buffer_lease_write_resolved_chars under the same lease.

Color entries use the low byte for a channel. Higher bits can preserve indexed-color or terminal-default intent. Do not discard that metadata when applying a raw color effect.

While a painted scene frame waits for commit, framebuffer access also requires its DONE ticket. The ticket identifies the finished frame. The lease keeps storage alive. Neither reports output delivery. Frames and output shows how those roles fit together.

Finish work before destroying owners#

An owner cannot disappear while work still needs it:

  • ot_session_destroy returns OT_CONTEXT_BUSY while the Session has pending output, an unrestored terminal, or a lease from ot_scene_frame_acquire_buffer_lease.
  • ot_context_destroy returns OT_CONTEXT_BUSY for any of those Session conditions, during an active native operation, and while any checked lease remains.

OT_CONTEXT_BUSY leaves the owner alive. Release leases and finish the Session’s work, then retry destruction.

For a terminal-managed Session, request ot_session_close and deliver restoration output before destruction. For a lost transport, use ot_session_cancel to discard pending output. Cancellation does not restore a terminal that no longer receives bytes, and it does not release your outstanding leases.

When no lease or Session blocks destruction, ot_context_destroy starts the shutdown of the Context’s host clipboard service. Native code cancels pending clipboard operations, and new ones return OT_CLIPBOARD_START_SHUTTING_DOWN. Destruction returns OT_CONTEXT_BUSY until the clipboard worker threads finish. The Context stays usable. Retry destruction.

In direct Zig code, pointers from Context.raw() getters are borrowed. The getters check handles, but they do not retain objects or pin storage. Raw access is outside the Context’s mutation and frame checks, so those checks do not protect it. Destroy the resource through the Context, not through the raw object’s destructor. Keep the allocator, std.Io, callback data, and borrowed environment state alive until the Context no longer needs them. Host I/O and time defines the native file and clock dependencies.

Set limits and keep calls on the owning thread#

ot_context_options sets two required limits:

  • object_capacity is the initial count of native resource slots, including checked leases. A full table doubles, up to 4,194,304 slots or object_capacity, whichever is larger.
  • render_cells_max limits each drawing buffer or renderer size, not the sum of cells across the Context.

Both limits must be positive. Zero does not select a default.

C Contexts also limit checked leases to 4,096 and distinct leased storage to 64 MiB. That storage includes retired allocations and tracker capacity, but not shared Context pools. These limits are not a total memory or frame-time budget. Sessions have their own output limits.

Each C Context allocates from its own allocator. Core’s getAllocatorStats() reports only the process allocator, which holds resources outside a Context such as standalone span feeds. Its counts do not include Context memory.

Call a C Context only from its creating operating-system thread, including for error queries and destruction. Separate Contexts can run on separate threads. Direct Zig Contexts permit ownership transfer only while idle and require serialized access. Clipboard worker threads allocate from the Context allocator, so a direct Zig Context with a clipboard service needs a thread-safe allocator.

Core uses the same ownership model through its own TypeScript wrappers. How Core uses native covers ResourceContext, resource factories, and scoped array access for Core extensions.

Changes0.6.0
0.6.0
Added OT_CLIPBOARD_START_SHUTTING_DOWN, OT_INVALID_PHASE, OT_LAYOUT_PAINT, OT_LAYOUT_PUBLIC, OT_LAYOUT_YOGA, OT_SCENE_CUSTOM and 41 more.