Skip to content

ecs: order on System.render, one paint-ordered draw across archetypes - #220

Open
krisnye wants to merge 2 commits into
mainfrom
knye/render-order
Open

krisnye wants to merge 2 commits into
mainfrom
knye/render-order

Conversation

@krisnye

@krisnye krisnye commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Adds order to System.render, so one draw covers several archetypes in an index's order. A 2D editor needs this to blend translucent shapes, images and text back to front.

Contract change

  • System.render's declaration takes order?: Index<{ sorted: true; key: Component.Key[] }>, constrained as System.create's order is. A hash index or a value that isn't an index is a type error, and throws at creation.
  • With order, the body's tables holds exactly one table, even when nothing matches. Its count is every matching row across archetypes, with has/not applied, and its bind makes instance i the i-th of them in index order.
  • Shaders are unchanged: the accessors keep fn name(row: u32), and row is the position in the order. Gpu.wgsl emits a different record and an ecs_address helper for an ordered system. Its output for unordered systems is unchanged byte for byte.
  • A run fails in its report when a matching archetype holds more rows than fit beside its slot in 32 bits: 2^31 with up to two matching archetypes, 2^30 with up to four, and so on.
  • The package version is 0.2.0. This supersedes ecs: version 0.1.1 #219: merge it instead, or merge ecs: version 0.1.1 #219 first and I'll rebase.

Performance (chromium, 50K entities in two archetypes)

Ordered Unordered, as two tables
Unchanged frame 0.030–0.036 ms 0.045–0.049 ms
One key changed 0.268–0.294 ms 0.042–0.050 ms

An unchanged frame writes nothing to the GPU and allocates nothing. A changed key rebuilds the order list (0.17 ms) and writes its 200 KB (0.07 ms).

Tests

  • render-order.browser.test.ts, covering:
    • two archetypes interleaved in depth;
    • has/not;
    • a despawn that moves a row, a spawn, a key change, and a hidden entity shown;
    • exactly one table, even when nothing matches;
    • writes only on change;
    • the ordered WGSL compiling.
  • Type tests for sorted, hash, unknown and non-index orders.
  • pnpm run build and pnpm test in packages/ecs pass: 134 files and 1010 tests.

🤖 Generated with Claude Code

krisnye and others added 2 commits October 22, 2026 00:11
Contract change (authorized): System.render's declaration takes
`order?: Index<{ readonly sorted: true; readonly key: readonly Component.Key[] }>`.
With it, the body's `tables` holds exactly one table whose `count` is every
matching row across archetypes (has/not apply) and whose `bind` makes instance
i the i-th of them in the index's order. Shader accessors keep their names and
`fn name(row: u32)` signatures; `row` is the position in the order and
`ecs_count()` the instance count. Gpu.wgsl emits, for an ordered system, the
record `struct EcsTable { ecs_rows, ecs_order, ecs_tables, ecs_shift }` and an
`ecs_address` helper; unordered output is unchanged. A non-sorted or unknown
index is a type error and throws at creation; a run whose matching archetype
holds more rows than 32 bits name beside its slot fails in its report.

Implementation: an order block of one u32 per instance (slot above the
record's shift, row below) and a tables block of each slot's column word
addresses, both heap blocks through uploadIndexes. The order is listed and
written only when the index's `changed` tick or a new `moved` tick (an entry
relocated with its key unchanged) moves, or the matches grow; the tables block
only when an address changes; the record only when a word does. An unchanged
run writes nothing and allocates nothing.

Numbers, chromium: gpu ordered render unchanged 50K 0.030-0.036 ms a run
(unordered two tables 0.045-0.049); gpu ordered render one key 50K
0.268-0.294 ms (listing 50K entries 0.17 ms, 200 KB write 0.07 ms).

Tests: render-order.browser.test.ts (two archetypes interleaved in depth,
has/not, despawn row move, spawn, key change, a hidden entity shown, one
table when nothing matches, writes only on change, ordered WGSL compiles),
system.type-test.ts (sorted, hash, unknown and non-index orders), a creation
check in create.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant