Lesson 13

Fragmentation

How relationships and hierarchies can spread a handful of entities over dozens of tables, what that costs, and what DontFragment and Parent do about it.

Every distinct type gets its own table. With plain components that’s fine: games use a limited set of combinations. Pairs change that, because (Faction, Red) and (Faction, Blue) are different ids, and so are (ChildOf, PartyA) and (ChildOf, PartyB). One relationship with n targets can split an otherwise identical set of entities over n tables. That is fragmentation.

Fragmentation isn’t wrong. Matching (Faction, Red) is then a whole-table match, as fast as matching a component. But it costs in three places:

  • Iteration: a system that doesn’t care about factions still gets one callback per table, each with only a few rows.
  • Table creation: every new target means a new table, and every cached query is notified.
  • Memory: each table has its own columns and bookkeeping.

Relationships: fragmenting vs DontFragment

Faction is an exclusive relationship (an entity has one faction at a time). Six entities get factions, then a frame runs two systems: Render over everything with Position, and RedTeam over the red entities. Watch the stats line and the number of system callbacks, then switch the setup to DontFragment and run it again.

Loading the flecs debugger…

  • Fragmenting: one table per faction, so Render is called once per table. When T2 switches from Blue to Red (Exclusive replaces the old target), it moves table.
  • DontFragment: the Faction pair isn’t part of the type at all. All six entities share [Position], the pair lives in sparse storage (shown under the tables), and switching faction moves nothing. Render is called once. RedTeam can no longer match a table; it checks entities, so it’s called for the red entities only.
Try it in the sandboxUnits in factions, with a system for all of them and one for the red team. Change factions and watch tables appear and empty. Then define your own relationship with DontFragment storage and move the units over to it.

Hierarchies: ChildOf vs Parent

ChildOf is a relationship, so it fragments like any other: every parent gets its own table of children. Current flecs also has Parent hierarchies, where the parent is a component value (flecs::Parent{party}) instead of part of the type. Switch between the two and compare the tables and the Render callbacks:

Loading the flecs debugger…

With ChildOf, six units under four parties take four tables, and Render is called once per table. With Parent, all the units share one table (with a (ParentDepth, @1) pair that records the depth; the @ marks a value, not an entity id), and the children are stored on the parent’s side. Queries that don’t care about the hierarchy run as if there was none.