Filters
Filters provide the logic for filtering entities in Queries.
Due to the archetype-based architecture of Arche 😉, filters are very efficient. Instead of against every single entity, they are only matched against archetypes.
The following sections present the filtering options available in Arche.
Filters should be stored and re-used where possible, particularly over time steps. Contrary, Queries are for one-time utilization and must be created from a filter before every iteration loop.
Core filters
Mask
The most common filter is a simple
ecs.Mask
, which is usually generated with the function
ecs.All
:
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6mask := ecs.All(posID, headID)
7
8query := world.Query(mask)
9query.Close()
Simple
ecs.Mask
filters match all entities that have at least all the specified components.
The generic equivalent is a simple FilterX, e.g.
generic.Filter2
:
1world := ecs.NewWorld()
2
3filter := generic.NewFilter2[Position, Heading]()
4query := filter.Query(&world)
5query.Close()
In both examples, we filter for all entities that have Position
and Heading
,
and anything else that we are not interested in.
Without
Particular components can be excluded with
ecs.Mask.Without
and
generic.Filter2.Without
:
1filter := generic.NewFilter1[Position]().
2 Without(generic.T[Heading]())
3
4_ = filter
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6mask := ecs.All(posID).Without(headID)
7_ = mask
Here, we filter for all entities that have a Position
, but no Heading
. Other components are allowed on the entities.
Exclusive
With
ecs.Mask.Exclusive
and
generic.Filter2.Exclusive
,
we can exclude all components that are not in the filter:
1filter := generic.NewFilter2[Position, Heading]().
2 Exclusive()
3_ = filter
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4headID := ecs.ComponentID[Heading](&world)
5
6mask := ecs.All(posID, headID).Exclusive()
7_ = mask
I.e., we get only entities with exactly the given components, and no more.
With & Optional
With the ID-based API, queries allow access to any component, irrespective of whether it was included in the query.
Generic queries, however, can access only the queried components.
Therefore, generic filters can have optional components through
generic.Filter2.Optional
:
1world := ecs.NewWorld()
2
3filter := generic.NewFilter2[Position, Heading]().
4 Optional(generic.T[Heading]())
5
6query := filter.Query(&world)
7for query.Next() {
8 _, head := query.Get()
9 if head == nil {
10 // Optional component Heading not present
11 fmt.Println("Heading not present in entity ", query.Entity())
12 }
13}
Note that the now optional Heading
must be specified also in the original filter.
In case an optional component is not present, Get
returns nil
for it.
Further, generic filters have
generic.Filter2.With
.
This requires the respective component(s) to be present, but they are not obtained through Get
:
1world := ecs.NewWorld()
2
3filter := generic.NewFilter1[Position]().
4 With(generic.T[Heading]())
5
6query := filter.Query(&world)
7for query.Next() {
8 pos := query.Get()
9 _ = pos
10}
Relation filters
Filters for Entity Relations are covered in the respective chapter.
Logic filters
Package
filter
provides logic combinations of filters.
Logic filters can only be used with the ID-based API.
Here are some examples:
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4velID := ecs.ComponentID[Velocity](&world)
5headID := ecs.ComponentID[Heading](&world)
6
7// Either Position and Velocity, or Position and Heading.
8_ = filter.OR{
9 L: ecs.All(posID, velID),
10 R: ecs.All(posID, headID),
11}
12
13// Same as above, expressed with a different logic.
14_ = filter.AND{
15 L: ecs.All(posID),
16 R: filter.Any(velID, headID),
17}
18
19// Missing any of Position or Velocity.
20_ = filter.AnyNot(posID, velID)
Filter caching
Normally, when iterating a Query, the underlying filter is evaluated on each archetype. With a high number of archetypes in the world, this can slow down query iteration and other query functions.
To prevent this slowdown, filters can be registered to the
ecs.World.Cache
via
ecs.Cache.Register
. For generic filters, there is
generic.Filter2.Register
:
1world := ecs.NewWorld()
2
3filter := generic.NewFilter1[Position]()
4filter.Register(&world)
5
6query := filter.Query(&world)
7query.Close()
1world := ecs.NewWorld()
2
3posID := ecs.ComponentID[Position](&world)
4
5mask := ecs.All(posID)
6filter := world.Cache().Register(mask)
7
8// Use the registered filter in queries!
9query := world.Query(&filter)
10query.Close()
For registered filters, the list of matching archetypes is cached internally. Thus, no filter evaluations are required during iteration. Instead, filters are only evaluated when a new archetype is created.
When a registered filter is not required anymore, it can be unregistered with
ecs.Cache.Unregister
or
generic.Filter2.Unregister
, respectively.
However, this is rarely required as (registered) filters are usually used over an entire simulation run.