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.

Important

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.