Ark
Concepts

Concepts

This chapter gives a brief explanation of ECS concepts and how they are represented in Ark.

The World

The World (ecs.World) is the central data storage in Ark. It manages and stores entities (ecs.Entity), their components, as well as Resources.

To create a world with default settings, use ecs.NewWorld:

1world := ecs.NewWorld()
2_ = world

A world can also be configured with an initial capacity for archetypes, the entity list, etc:

1world := ecs.NewWorld(1024)
2_ = world

For systematic simulations, it is possible to reset a populated world for reuse:

1world := ecs.NewWorld()
2// ... do something with the world.
3
4world.Reset()
5// ... start over again.

Entities

Entities (ecs.Entity) are used to represent the “objects” in a game or simulation. In Ark, an entity is just an opaque ID (with an associated current generation) that allows access to components associated to it.

Entities without any components can be created through the (ecs.World):

1entity := world.NewEntity()
2_ = entity

For creating entities with components, component mappers are used. Entities can be removed or deleted like this:

1world.RemoveEntity(entity)

Entities can be stored safely, in components or elsewhere. However, always store them by value, never by pointer. When dealing with stored entities, it may be required to check whether they are still alive:

1alive := world.Alive(entity)
2if alive {
3	// ...
4}

In Ark, entities are returned to a pool when they are removed from the world. These entities can be recycled, with the same ID (Entity.ID), but an incremented generation (Entity.Gen). This allows to determine whether an entity held by the user is still alive, despite it was potentially recycled.

Components

Components contain the data, or state variables, associated with an entity. Each entity can have an arbitrary combination of components, but can only have one instance of each. Components can be added to and removed from entities at runtime.

Components are identified by their type. So e.g., all instances of Position are of the same component type. Each entity can have only one Position.

Components are simple Go structs and can contain variables of any type, including slices and pointers. Typically, components don’t have any functions. Particularly, contrary to object oriented programming (OOP), components don’t contain game or simulation logic. In ECS, logic is performed by systems, using queries.

For optimal performance and modularity, components should be small and only contain closely related state variables that are typically used together. “Good” components are e.g. Position, Velocity, Age, Sex, etc. “Bad” components are large monolithic things with many state variables like Player or Animal.

Components can also be labels or tags, which means that they don’t contain any data but are just used to tag entities, like Female and Male.

See chapter Component operations for how to create entities with components, adding and removing components, and other details.

Queries

Queries are the core feature for writing logic in an ECS. A query iterates over all entities that possess all the component types specified by the query. Note that these entities may contain further components, which are ignored.

For best performance, filters are used to create queries:

 1// Create a filter.
 2filter := ecs.NewFilter2[Position, Velocity](&world)
 3// Obtain a query.
 4query := filter.Query()
 5// Iterate the query.
 6for query.Next() {
 7	pos, vel := query.Get()
 8	pos.X += vel.X
 9	pos.Y += vel.Y
10}

Filters are relatively costly to create, as they require lookup of component IDs. This takes around 20ns per component. Thus, make sure to create filters only once and store them, e.g. in systems. Then, create a new query from the filter each time before the iteration loop.

Important

The component pointers obtained from a query should never be stored outside of the current context (i.e. the query loop), as they are not persistent inside the world.

For advanced filters, caching and other details, see chapter Filters & queries.

Systems

Systems perform the logic of your game or simulation, using queries. Ark does not provide systems or a scheduler for them. You can create your own interface for system, matching your game engine if you are using one. Alternatively, ark-tools provides systems, a scheduler, and other useful stuff for Ark. See there for a full example.

Resources

Resources are data structures that are unique to an ECS world. Examples could be the current game/simulation tick, a grid that your entities live on, or an acceleration structure for spatial indexing. As such, they can be thought of as components that exist only once and are not associated to an entity.

As with components, resources are Go structs that can contain any types of variables.

 1// Create a resource.
 2var worldGrid Grid = NewGrid(100, 100)
 3// Add it to the world.
 4ecs.AddResource(&world, &worldGrid)
 5
 6// In your system, create a resource mapper.
 7gridRes := ecs.NewResource[Grid](&world)
 8// Access the resource.
 9grid := gridRes.Get()
10_ = grid

Relationships

Entity relationships are a powerful, advanced ECS feature that was first introduced by Flecs. They serve the efficient representation of entity hierarchies, groupings, or other relationships.

Relationships can also be realized by storing entities (or lists of entities) in components. However, Ark’s relations feature allows for more efficiency and more comprehensible logic, as relationships can be used in queries.

Some illustrative examples:

  • Iterate/get all game objects in a grid cell.
  • Iterate/get all animals in a herd.
  • Iterate/get all plants of a certain species.
  • Build hierarchies, like a scene graph.

Compared to Flecs, entity relations in Ark are more limited. Each entity can have an arbitrary number of relationships to other entities, but for each relation type (i.e. relation component), there can be only one target entity. This is primarily a performance consideration.

For usage and more details, see chapter Entity relationships.