A store is an explicitly located directory the host holds a handle to, so
every relative asset path means the same thing however the process was
launched. The host never calls chdir, and a path that would escape the
store is refused rather than rewritten.
Textures are the shared texture type from the companion roc-ray-types
package, re-exported here as Assets.Texture. Releasing the final reference
to one unloads the native texture automatically, so there is no unload to
remember.
The two effects that read the disk -- Store.open! and load_texture! --
wait: each is legal in init!, where it blocks startup, and in tasks, where
it parks the task; both are refused in update! and render!. Everything
else here builds a texture from bytes the app already holds --
texture_from_bytes!, generate_color_texture!,
generate_checked_texture!, update_texture! -- and is legal in init!,
update!, and tasks, and refused in render!, where a decode or an upload
would land in the middle of drawing a frame.
That split is what a texture loaded after startup goes through: read the
file on a task with Files.read_bytes!, return the bytes as the task's
message, and call texture_from_bytes! from update! when the message
arrives. Calling load_texture! inside the task itself does the same in one
step, which is what a hot reload wants -- poll Files.metadata! on a task,
and load again when the modification time moves.
ResourceLimit on any of them means the host's fixed texture table is full.
It is a bound on how many textures exist at once, not on how fast they are
made, so it is answered rather than retried: release a texture the app no
longer draws, or load fewer.
Start from an application/executable-relative asset directory. This is the
normal packaged-app choice: the assets travel with the executable, so the
store resolves the same way however the app was launched.
Start from a directory relative to the process working directory. This is
what running an example from the repository root wants, and what a tool
invoked from a project directory wants; it moves with the shell rather
than with the executable.
Require this store's roc-assets.manifest to match an expectation, so a
mismatched or half-updated asset set fails at startup rather than as a
missing texture later.
How many bytes an RGBA pixel list occupies on the wire: four per pixel.
An upload borrows the list rather than copying it, so this is what an app
measures a update_texture! or update_texture_region! against when it
is budgeting per-frame pixel traffic.
Legal in init!, where it blocks startup, and in tasks, where it parks
the task; refused in update! and render!. The file is read off the
frame thread; the decode and the GPU upload happen when the bytes are
back. To load after startup, call this inside Task.spawn!, or read the
bytes on a task and call texture_from_bytes! from update!.
path must be relative; PathInvalid is an absolute path, one holding a
NUL, or a lexical .. escape, and is answered before any file I/O.
NotFound is no such file under the store, ReadFailed is a file that
is there and could not be read, and TextureLoadFailed is bytes raylib
would not decode as an image.
A host-owned GPU texture: an opaque, reference-counted native handle plus
the pixel width and height, kept on the value so layout and
source-rectangle math stays pure.
This is the shared texture type from the companion roc-ray-types
package, re-exported so an app can name it without depending on that
package as well. Draw.Texture is the same type under a second name, and
a package written against the package's own Texture unifies with both.
An opened, explicitly located disk asset store. The host retains the
directory handle, not the process working directory; every relative asset
lookup is made through that handle.
Open the store described by a StoreConfig, checking its manifest if
one was required.
Legal in init!, where it blocks startup, and in tasks, where it
parks the task; refused in update! and render!. Opening the
directory and reading the manifest are filesystem work, so the host
does both off the frame thread and answers when they are done.
The first four failures are about the root directory: RootNotFound
is nothing at that path, RootNotDirectory is something there that
is not a directory, RootUnreadable is a directory the process may
not open, and InvalidRootPath is a path this host will not accept
at all -- one holding a NUL, or a relative form that escapes.
The rest are about the roc-assets.manifest a RequireManifest
config asked for. ManifestMissing is no manifest beside the assets,
ManifestUnreadable is one that could not be read, and
ManifestMalformed is one that is not a manifest. Of the four
comparisons, AssetSetMismatch is a manifest describing a different
asset set than the one expected, SchemaMismatch a manifest written
to a different schema version, ContentVersionMismatch a different
content version, and ContentHashMismatch a declared content hash
that is not the expected one. InvalidExpectedContentHash is the
expectation itself being unusable -- a Sha256 string that is not 64
hexadecimal characters.
A Sha256 expectation compares against the manifest's declaration
only. Nothing walks or hashes the loose files, so opening a store
stays constant-time in the number of assets.
stub is what every host resource in the platform calls its
resource-free test value, so a model full of assets can be written
down in an expect.
The handle never resolves to an open directory, so every load made
through it fails the way a load through a released store does. It
exists for the app that keeps a store in its model, to let a pure
expect build that model. Do not use it to test asset resolution or
resource lifetime.
How a disk store root is resolved. These choices are explicit so moving an
executable, changing CWD, and selecting a mod directory cannot silently
change one another's meaning. The host never calls chdir.
Whether opening a store checks the asset-set manifest named
roc-assets.manifest beside it. IgnoreManifest does not look;
RequireManifest fails the open unless the manifest is there and matches
the expectation.
How closely a manifest's declared content has to match. AnyContent
deliberately leaves it unconstrained, which is what a directory of loose
files under development wants. Sha256 carries the 64-character
hexadecimal digest the manifest must declare.
What a RequireManifest open expects the manifest to say: which asset
set it describes, which schema version it was written to, which content
version it is, and which content it declares.
Where a store's root is and whether its manifest is checked. A plain
record; build it with beside_executable, working_directory or
absolute_directory, and add an expectation with with_manifest.
An authored image embedded with a compile-time file import, tagged with
its format. The format is stated rather than sniffed, so a mislabelled
file fails to decode instead of decoding as something else.