Queries & Streams
one Aspect per Query – no double-dipping!
Both World and Aspect implement IAspect, which carries the entire familiar query surface: the Query() / Query<C1...C5>() builders, Stream<...>(), and the universal All Query.
Querying an Aspect
You usually don't even have to say which Aspect you mean – world.Query<T>() resolves to the Aspect that owns T all by itself:
// Position is owned by "visuals" - this Query targets "visuals" automatically
var positions = world.Query<Position>().Compile();
var crewSum = 0;
world.Stream<CrewData>().For((ref crew) => crewSum += crew.Count);var positions = visuals.Query<Position>().Compile();
var xSum = 0f;
visuals.Stream<Position>().For((ref position) => xSum += position.X);Both routes are equivalent – pick whichever reads better. (They're literally the same Query – see the shared cache.)
The Single-Aspect Rule
Here's the one commandment: a Query can only match component types stored in a single Aspect. Ask for types from two different storage universes and fennecs will refuse, with an itemized receipt:
THOU SHALT NOT SPAN ASPECTS
world.Query<Position, CrewData>().Compile(); // 💥 InvalidOperationException:
// A Query can only match Component types stored in a single Aspect,
// but this Query's types span several:
// Position -> Aspect "visuals"
// CrewData -> Aspect "game"
// Group hot data into the same Aspect, or access other Aspects'
// components via entity.Ref<T>() inside the loop.Why? An Aspect is its own memory universe with its own Archetypes – there simply is no contiguous storage that holds both types side by side. That contiguity is the entire point of Aspects, so the rule is less a limitation and more the feature's job description.
Filters count too
Has, Not, and Any filters are part of the Query's type mask, so they play by the same rule:
world.Query<Position>().Has<CrewData>().Compile(); // 💥 spans "visuals" + "game"
// filtering WITHIN one Aspect is business as usual:
var loaded = world.Query<CrewData>().Has<Cargo>().Compile(); // ✅ all "game"
var empty = game.Query<CrewData>().Not<Cargo>().Compile(); // ✅ still "game"Relations work per-Aspect as well – including Match Expressions like Match.Entity – and may target Entities that never joined the Aspect at all.
Crossing Aspects inside a loop
Need data from another Aspect while iterating? The sanctioned move is entity.Ref<T>() – Entity CRUD sees across all Aspects:
// hot loop over contiguous "visuals" data...
visuals.Stream<Position>().For((in entity, ref position) =>
{
// ...with an occasional peek into the "game" universe
if (!entity.Has<CrewData>()) return;
position.Y += entity.Ref<CrewData>().Count * 0.1f;
});This is a random-access read, so keep it to the cold side of your data. If you find yourself Ref-ing a type in every iteration of every frame... congratulations, you've discovered hot data! Move it into the same Aspect.
Shared Query Cache
A World Query and its Aspect-side twin aren't just equivalent – they're the same object, served from the same per-Aspect cache:
var viaWorld = world.Query<Position>().Compile();
var viaAspect = visuals.Query<Position>().Compile();
// ReferenceEquals(viaWorld, viaAspect) == true
BEHIND THE SCENES
Each Aspect keeps its own Query cache and notifies its cached Queries when new Archetypes appear – compile a Query first and spawn matching Entities later, and the Query picks them up just fine. And since a World delegates everything to Main, world.All is world.Main.All. The universal aspect.All of every other Aspect matches that Aspect's members only.
Design Tips
- Group components that are queried together into the same Aspect. The single-Aspect rule is your layout linter: if a Query wants types from two Aspects, either those types belong together, or the Query wants too much.
- Start with
Main, carve out later – but remember ownership freezes at first use, so do the carving in your World's constructor. - Keep cross-Aspect reads occasional.
entity.Ref<T>()is the pressure valve, not the plumbing.
Where to next?
That's the whole tour! Head back to the Aspects overview, or check what else lurks in Advanced.