Expand description
The object-source abstraction shared by the synchronous and asynchronous APIs.
pdfboss keeps exactly one implementation of every algorithm that reads a
document — text extraction, rasterization — and two ways of delivering
bytes to it. ObjectSource is what a caller holding the whole file
provides. AsyncObjectSource is what a caller streaming from a file or
a network provides. Algorithms are written once against the asynchronous
trait and reach synchronous callers through Immediate and
block_on.
block_on is a complete single-threaded driver: it polls, and on
Poll::Pending parks the calling thread until a waker unparks it. A
future built over Immediate resolves every leaf against a
std::future::Ready and so completes on its first poll, never reaching
the parking path — but that is an optimisation, not a precondition. Any
future whose wakeup does not depend on an external reactor is driven
correctly; one that does will park forever, so see block_on’s own
documentation before handing it a future from elsewhere.
Nothing here needs an async runtime: Future, Pin, Box, Arc,
Wake and std::future::ready are all in the standard library, so
pdfboss-core stays free of executor dependencies.
§Signing a shared algorithm
Every algorithm written against AsyncObjectSource follows the same three
rules. They are not stylistic: each one is what makes a single
implementation serve both a synchronous and an asynchronous caller.
-
Entry points take the source by value —
src: S, never&S. A future holding&'a SisSendbut never'static, and the asynchronous consumers (spawning onto a runtime, crossing into the Python bindings) need both. It costs nothing: an asynchronous document is anArchandle, andImmediateover a borrowed document isCopy.An entry point is a function a consumer awaits directly. Helpers inside one take
&S, because the entry point owns the source and a future may borrow across its own awaits freely — the'staticquestion is settled at the outermost boundary, not at every internal call. That does putS: Syncon the entry point’s future beingSend, which costs nothing either:resolve_withalready requiresSyncof every genuinely asynchronous source. One entry point calling another passes&src, which works because&Sis itself anAsyncObjectSource. -
Put no
SendorSyncbound on the function. Auto traits are inferred per instantiation, so one function yields aSendfuture over an asynchronous source and a non-Sendfuture overImmediate<&Document>— which is correct, becauseblock_ondrives the latter on the calling thread and never sends it anywhere. -
Call
src.resolve(o), notresolve_with, unless the algorithm genuinely needsS: Syncfor other reasons.resolve_withrequiresSyncand so excludesImmediate<&Document>; reaching for it out of habit silently breaks the synchronous path.
crate::document::page_content_with is the reference example.
Structs§
- Immediate
- Presents a synchronous
ObjectSourceas anAsyncObjectSourcewhose futures are already complete.
Constants§
- MAX_
RESOLVE_ DEPTH - Reference-chase depth limit for every reference chase in the crate:
resolve_sync_with,resolve_with, the providedObjectSource::resolveandcrate::document::Document::resolve.
Traits§
- Async
Object Source - Reads indirect objects and decodes streams, awaiting whatever I/O that takes.
- Object
Source - Reads indirect objects and decodes streams, with the whole file already available.
Functions§
- block_
on - Runs
futureto completion on the current thread. - resolve_
sync_ with - Chases reference chains against a synchronous source, depth-capped at
MAX_RESOLVE_DEPTH. - resolve_
with - Chases reference chains against an asynchronous source, depth-capped at
MAX_RESOLVE_DEPTH.
Type Aliases§
- BoxFuture
- A boxed future, as returned by every
AsyncObjectSourcemethod.