Asset loading
CE::Assets::Manifests::Reader parses graphics manifests 2.0 into CPU-only
sprite/tileset definitions and shader/material recipes. An owned
CE::Assets::Loading::Loader keeps one immutable root:
preparation selects indexed documents, decodes owned RGBA pixels and snapshots
shader source bytes without a provider; upload() constructs resources and
publishes retained metadata on the provider’s loading thread.
Runtime entry point
After initializing the selected backend, load a root with its provider:
CE::Assets::Loading::Loader loader("assets");
loader.load_assets(engine.resources());
auto actor = CE::Assets::Sprites::Manager::get().get_asset("miniworld:soldier_swordsman_cyan");
auto documents = loader.manifests(); // shared_ptr<const vector<CE::Assets::Manifests::Graphics>>
Each asset family owns its definitions, retained values and cache API. Sprite and
tileset managers expose load() for definitions; the image manager exposes
load() for paths and upload() for decoded pixels. Their inherited get_asset()
and clear_assets() retain the shared cache contract. The loader’s
load_assets() remains the complete-root preparation/upload convenience path.
The value guide maps the family names.
Each preparation scans again. Only exact graphics-manifests.json indexes select
documents; schemas and unlisted JSON are not parsed. Index, texture and shader
references resolve from the enclosing graphics/ directory, and asset keys use
namespace:name. prepare() selects every discovered index and decodes referenced
images plus standalone PNGs below the root, deduplicated in sorted order.
prepare_graphics(indexes) selects explicit indexes relative to the Loader root
(or supplied absolute paths), decoding only their referenced images. A shader-only
selection decodes no images. Cross-document
IDs and every grid’s bounds are checked against those exact decoded dimensions
before any upload. Upload consumes the owned pixels without reopening image files.
Pixels are four-channel RGBA in the decoder’s default top-to-bottom row order.
OpenGL reverses those rows in transient upload storage to match the atlas
geometry’s upward-positive UV coordinates; the supplied pixels remain unchanged.
The separate stb alpha-atlas path preserves its baked row/UV convention.
Shader sources resolve through exact registered paths, including when basenames
repeat. Preparation owns the bytes, so upload does not reopen shader files either.
Program/material kinds, references and asset selections are checked within the
selected batch; an unrelated cached material cannot satisfy a missing selection.
register_files() performs discovery/publication of unmanaged paths only, without
opening JSON, decoding images or requiring a provider.
Preparation can use a tracked context worker group. Own both the loader and its
CE::Assets::Loading::Prepared value through the platform handoff; simulation only retrieves ready
futures, and never blocks on CPU preparation or GPU upload:
auto workers = engine.make_worker_group();
auto platform = engine.platform_dispatcher().submission();
auto loader = std::make_shared<CE::Assets::Loading::Loader>("assets");
auto preparing = workers.submit([loader, platform] {
auto prepared = loader->prepare();
return platform.submit(
[loader, prepared = std::move(prepared)](CE::Runtime::Context& owner) mutable {
loader->upload(std::move(prepared), owner.resources());
return loader->manifests();
}
);
});
// A later update: only after preparing.wait_for(0s) reports ready, get the
// returned upload future. Check that future's readiness before reading metadata.
An owned root or injected shared root supplies physical capacity; this group belongs to the context shutdown domain. Accepted CPU jobs finish while the platform dispatcher remains available. Any final unexecuted upload is cancelled before resource teardown, so its future reports failure instead of hanging. Use platform submission endpoints rather than borrowed dispatcher pointers.
Publication and retry
CE::Assets::Loading::Prepared owns decoded pixels, shader bytes and definitions, with no provider or native
handles. Preparation failure changes no caches or published metadata. Upload
consumes the supplied value on the provider’s loading owner; moving it into a
request transfers that ownership. Retry needs a fresh preparation or a preserved
copy. Pending platform requests can cancel before execution under the
dispatcher contract; executing uploads have no
rollback or mid-batch cancellation contract.
Each created cache entry can become visible before the next entry is created. Upload failure preserves completed entries and the last successful metadata snapshot. Allocation of that final snapshot can fail even after all entries exist. Retry skips existing keys rather than replacing them, so published manifests describe submitted definitions rather than an atomic view of current caches. Changed files under the same keys do not provide hot reload. Readers retain old metadata snapshots after another upload or loader destruction.
Upload checks the optional provider-owned shader_asset_builder() capability,
then publishes programs/materials before images and sprite/tileset resources.
Providers without that capability reject selected shader recipes explicitly.
CE::Assets::Shaders::Manager retains each recipe together with its executable generation;
material construction uses the retained program generation even when an ordinary
load preserves an existing program. Asset selections retain their material handle.
upload(prepared, provider, true) explicitly replaces shader generations only.
Each candidate is complete before publication; failure preserves that key’s old
generation, while earlier successful replacements remain published. Existing
assets and frames keep their old handles rather than automatically rebinding.
shader_manifests() and manifests() describe the last successful submitted
definitions, not an atomic cache snapshot. diagnostics() reports the last attempt,
including partial publications/replacements. There is no watch-based reload or
whole-batch rollback.
load_assets(provider) is the synchronous prepare/upload convenience path.
Construct separate loaders for separate roots. Legacy Loader::get(root) remains
available but rejects a different root after its first initialization; get() only
retrieves an already-initialized singleton.
Resources and application bootstrap
The generic loader loads indexed shaders/materials, images, sprites and tilesets.
The application still chooses fonts explicitly; the
file/font discovery guide defines candidate selection,
index freshness and the legacy font cache’s default/collection-face policy. The demo selects
graphics/shaders/graphics-manifests.json and retrieves main:text and main:images
from CE::Assets::Shaders::Manager::find_material(), including with --full-assets. F5 prepares that same index
and requests explicit replacement. The demo adopts its pair only after both
replacements succeed; failed reload preserves its previous application pair even
if the shared catalogue has published an earlier candidate. Fonts remain outside
the graphics manifest contract. Manual material builders remain supported; the
rendering guide
shows the UI example that deliberately retains this path.
CE::Rendering::Resources::Provider::create_image() accepts
CE::Assets::Images::Decoded RGBA pixels.
upload_geometry(span<const CE::Rendering::Geometry::Vertex2D>, topology) copies the transient view before
returning. The older shared-pointer/count overload keeps its CPU owner only through
that upload. Atlas cells use independent four-vertex strips; images and glyphs use
six-vertex triangle quads. Backend handles never retain these CPU buffers. OpenGL
checks dimension, byte-count, vertex-count, and draw-range limits before use.
Typed manifest data and consumers
Engine definition types retain grid origins, frame sizes, spacing, pivots, views,
orientations, timed clips and facings, animated tile targets, Wang terrain metadata,
weighted variants, and neighbor-mask lookup tables.
CE::Assets::Sprites::Asset::definition() and
CE::Assets::Tilesets::Asset::definition() expose them after construction. Entity-owned
CE::Assets::Sprites::Animation already advances timed clips and publishes resolved cells; the
cached sprite has no mutable playback cursor. Tile-map neighbor selection and
application meanings for views/orientations remain gameplay work.
The asset value and playback contract defines direct
construction limits, borrowed metadata, lookup failure and submission ownership.
Use CE::Assets::Manifests::Reader::load(file) or parse(stream, source) for document-only tools.
Use the owning focused runners or all-tests for regressions;
architecture validation describes
selection and coverage limits.