Open
Description
What problem does this solve or what need does it fill?
#13417 adds sorting at an iterator level. These sorts:
- Do not persist across system runs (it will be common for sorted data to rarely be mutated).
- Make query iteration completely random access.
What solution would you like?
- Command to reorder tables.
- Rearrange tables for sorted queries by default.
- Make queries cache sorts & use change detection to early out of resorting.
- Condense sorts to
low..=high
ranges for cache friendly iteration. - Optionally cache iteration position to allow suspending and resuming iteration between system runs. This is useful for ui scroll areas and any rhythm game.
Final API should look something like this:
fn cmp(left: FilteredEntityRef, right: FilteredEntityRef) -> std::cmp::Ordering {
left.get::<A>().unwrap().cmp(&right.get::<A>().unwrap())
}
fn sys(mut query: Query<(&A, &B)>) {
for (a, b) in query.sort(cmp)
// Optional call that will prevent tables from being reorderd.
// Useful if you have multiple systems requesting slightly different sorts or
// just completely opting out of reordering & doing multiple ordering manually.
// User could do multiple stable sorts to get dense iteration across multiple systems.
.no_reorder()
.iter()
{
// ..
}
}
#[derive(Component)]
struct Offset(pub f64);
fn sort_by_offset(left: FilteredEntityRef, right: FilteredEntityRef) -> std::cmp::Ordering {
left.get::<Offset>().unwrap().0.cmp(&right.get::<Offset>().unwrap().0)
}
fn seek_by_offset(entity: FilteredEntityRef, needle: f64) -> std::cmp::Ordering {
entity.get::<Offset>().unwrap().0.cmp(&needle)
}
fn timeline(mut query: Query<(&Offset, &A, &B)>, time: Res<Time>) {
for (offset, a, b) in query.sort(sort_by_offset)
// Resume iteration from a previous run & update it for a future run.
// Assuming the following:
// - some cached pos: usize
// - a needle: T
// - a const window size N: usize
//
// seeking will:
// - Use linear search to search for the needle among nearest neighbors pos-N..=pos+N.
// - Fall back to binary search if the range doesn't contain the needle (we've jumped).
.seek(time.elapsed_seconds_f64(), seek_by_offset)
.iter()
.take(5)
{
// ..
}
}
What alternative(s) have you considered?
- Putting things in resources and doing these sorts myself. This is only viable when you've feature frozen your project and need no changes to data architecture. You completely lose the flexibility of ECS doing this.
- Despawning entities & reinserting them into tables. This first requires knowing all the components to take out of an entity and secondly relies on side effects. Table order is not part of the API contract.
Additional context
Missing prerequisite features:
World::parallel_commands
/make the world command queue thread local by default (needed to allow queries to do2.
).- Archetype level change detection: Accelerate change detection by redundantly storing change ticks #5097 (needed for
3.
).
Not required but helpful missing feature:
- Queries as entities (can avoid introducing the cache structure for queries that aren't sorted).
Metadata
Assignees
Labels
Entities, components, systems, and eventsA new feature, making something new possibleA change motivated by improving speed, memory usage or compile timesQuite challenging from either a design or technical perspective. Ask for help!This issue or PR is particularly complex, and needs an approved design doc before it can be mergedThere is active debate or serious implications around merging this PR
Activity