Every call stores its result in the uform_outcome_t it names: a status, and a static message that is NULL exactly on success. uform_completion_blocking_k, the default, returns with the status final and every output readable. uform_completion_deferred_k returns once the executor’s own thread took the call, and the status reads uform_status_deferred_k until the call is final. uform_outcome_status polls with acquire ordering and uform_outcome_wait blocks until final, and these are the only ways to read a status another thread may still store.
Submission copies the structure, while everything it points to stays borrowed until final.
An executor runs one call at a time: a call finding another one holding its executor finishes uform_status_contended_k without running, so a caller orders dependent calls by waiting on the first.
A call lets go of its arena, its handles and its executor before it stores the final status, so whoever reads it final may reuse all three at once.
A call stops early only when its uform_progress_t handler returns uform_cancel_k, and then finishes uform_status_cancelled_k.
uform_outcome_t outcomes[4] = {0};for (size_t shard = 0; shard != 4; ++shard) { // one executor per shard, each running one call at a time encodes[shard].outcome = &outcomes[shard]; encodes[shard].executor = executors[shard]; encodes[shard].completion = uform_completion_deferred_k; uform_image_encode(&encodes[shard]);}for (size_t shard = 0; shard != 4; ++shard) uform_outcome_wait(&outcomes[shard]);
The threads of an executor either include the thread that calls it or work without it.
A caller-inclusive pool needs its caller as one of its threads, so a deferred call on it runs at submission and is final when it returns.
A caller-exclusive pool has threads of its own: a deferred call returns as soon as the pool’s thread 0 takes it, and that thread runs the call, forks included, and stores the status. uform_executor_for_ram and uform_executor_for_vram spawn caller-exclusive pools wherever the build has threads, and WebAssembly’s single thread is inclusive.
Because a deferred call needs nobody to drive it, one thread can keep several executors busy, here across UForm, USearch and UStore, by only reading status words:
text_encode.completion = uform_completion_deferred_k; // UForm, on a VRAM executordense_search.completion = usearch_completion_deferred_k; // USearch, on a RAM executorblobs_read.completion = ustore_completion_deferred_k; // UStore, on an NVMe executoruform_text_encode(&text_encode);usearch_dense_search(&dense_search);ustore_blobs_read(&blobs_read);while (uform_outcome_status(text_encode.outcome) == uform_status_deferred_k || usearch_outcome_status(dense_search.outcome) == usearch_status_deferred_k || ustore_outcome_status(blobs_read.outcome) == ustore_status_deferred_k) prepare_next_batch();
API
Types
uform_outcome_tOutcome of one call: a status every binding can branch on, and a static message carrying the detail the status cannot.
A batch verb splits its items into steps nearly equal slices, hands each to its engine whole and reports after each; a load, whose engine counts its own tensors, forwards only the ticks that cross into the next slice. Without a handler the call runs as one slice and reports nothing.
uform_outcome_statusReads the status of outcome without waiting, with the acquire ordering its final store pairs with, so a final status also publishes every output of the call.
The one way to poll: the status word is atomic only by this contract, so a caller reading the field directly needs an atomic load its language may not offer on foreign memory.