Skip to content

RFC §unification: standalone Image component for entities via AssetCatalog #568

@apotema

Description

@apotema

Part of #560.

Today entities can only display images via atlas-sprites. The Sprite component carries a sprite_name resolved via TextureManager.findSprite, which only iterates atlases. There is no entity-side path for a standalone PNG to be displayed by the ECS render path. The workaround (wrap each loose image as a 1-sprite atlas) is ugly and bloats the resource list.

The AssetCatalog already has an image LoaderKind for standalone PNGs (decode-on-worker, upload-on-main, refcounted). What's missing is an entity component that consumes it.

Proposal

New Image component, separate from Sprite (chosen over extending Sprite for clarity — each component has one job).

"Image": {
    "name": "logo_splash",       // AssetCatalog asset key
    "pivot": "bottom_left",      // same enum as Sprite
    "layer": "ui",               // same layering as Sprite
    "z_index": 10,               // same
    "visible": true              // same; optional
}

v1 scope — deliberately narrow

  • No SpriteAnimation-style multi-frame animation.
  • No sprite_by_field-style dynamic name swapping.
  • No sub-rect cropping.

If any of those needs arises, the answer is "use Sprite + an atlas." Image is for static single-PNG entities and nothing else.

project.labelle declaration

.resources accepts both atlas and standalone-image entries; .json becomes optional. Presence of .json selects the atlas loader, absence selects the catalog image loader.

.resources = .{
    .{ .name = "rooms",        .json = "assets/rooms.json", .texture = "assets/rooms.png" },  // atlas
    .{ .name = "logo_splash",  .texture = "assets/logo.png" },                                  // standalone
}

Inference walker

Walker scans strings in both Sprite.sprite_name and Image.name against a unified reverse index:

const ResourceRef = union(enum) {
    atlas: []const u8,   // bundle name (atlas containing the sprite)
    image: []const u8,   // asset name (the standalone image)
};
const reverse_index = std.StringHashMap(ResourceRef);

Hits route to the appropriate loader (legacy TextureManager for atlas; AssetCatalog for image). Coordinates with #563 (asset inference walker) and #565 (engine API decisions).

Naming caveat

There's already a gui_types.Image in the imgui-layer GUI types. The ECS Image component is distinct (different module, different render path). Worth a one-liner in docs to avoid confusion. If the collision is judged too risky during review, alternatives: Picture, LooseSprite, Standalone.

Renderer work

The renderer needs an Image render branch:

  • Get the texture via AssetCatalog.getTexture(name).
  • Draw the full texture (no sub-rect, no atlas UV math).
  • Honour pivot, layer, z_index, visibility — same semantics as Sprite.
  • Skip rendering when assets.isReady(name) is false (matches the Q4 lazy pop-in model).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions