Ark
Cheat sheet

Cheat sheet

Frequently used Ark APIs for quick lookup.

World creation

The world is the central ECS data storage. Most applications will use exactly one world.

🌍 Create a World with default initial capacity
world := ecs.NewWorld()
_ = &world

API: World.New

🌍 Create a World with a specific initial capacity
world := ecs.NewWorld(1024)
_ = &world

API: World.New

Create entities

Entities represent the “objects” in a game or simulation.

✨ Create an entity without components

Create an entity without components:

e := world.NewEntity()
_ = e

API: World.NewEntity

✨ A component mapper is required for creating entities with components

Component mappers should be stored and re-used for best performance.

mapper := ecs.NewMap2[Position, Velocity](&world)
_ = mapper

API: Map1, Map2, …

✨ Create a single entity, given some components
e := mapper.NewEntity(
	&Position{X: 100, Y: 100},
	&Velocity{X: 1, Y: -1},
)
_ = e

API: Map2.NewEntity

✨ Create a single entity using a callback
e := mapper.NewEntityFn(func(pos *Position, vel *Velocity) {
	pos.X, pos.Y = 100, 100
	vel.X, vel.Y = 1, -1
})
_ = e

API: Map2.NewEntityFn

✨ Create many entities more efficiently, all with the same component values
mapper.NewBatch(
	100,
	&Position{X: 100, Y: 100},
	&Velocity{X: 1, Y: -1},
)

API: Map2.NewBatch

✨ Create many entities, using a callback for individual initialization
mapper.NewBatchFn(
	100,
	func(e ecs.Entity, pos *Position, vel *Velocity) {
		pos.X, pos.Y = rand.Float64()*100, rand.Float64()*100
		vel.X, vel.Y = rand.NormFloat64(), rand.NormFloat64()
	})

API: Map2.NewBatchFn

Remove entities

❌ Remove a single entity
world.RemoveEntity(entity)

API: World.RemoveEntity

❌ Remove all entities that match a filter
filter := ecs.NewFilter2[Position, Velocity](&world).Exclusive()

world.RemoveEntities(filter.Batch(), nil)

API: World.RemoveEntities

❌ With a callback to do something with entities before their removal
world.RemoveEntities(filter.Batch(), func(entity ecs.Entity) {
	// Do something before removal
})

API: World.RemoveEntities

Add/remove components

Components store the data that is associated to entities.

🧩 A component mapper is required for adding and removing components

It adds or removes the given components to/from entities. Component mappers should be stored and re-used for best performance.

mapper := ecs.NewMap2[Position, Velocity](&world)
_ = mapper

API: Map1, Map2, …

🧩 Add and remove components to/from a single entity
entity := world.NewEntity()

mapper.Add(
	entity,
	&Position{X: 100, Y: 100},
	&Velocity{X: 1, Y: -1},
)

mapper.Remove(entity)

API: Map2.Add, Map2.Remove

🧩 Add components to all entities matching a filter
filter := ecs.NewFilter1[Altitude](&world).Exclusive()

mapper.AddBatch(
	filter.Batch(),
	&Position{X: 100, Y: 100},
	&Velocity{X: 1, Y: -1},
)

API: Map2.AddBatch

🧩 Add components to all entities matching a filter, with individual initialization
filter := ecs.NewFilter1[Altitude](&world).Exclusive()

mapper.AddBatchFn(
	filter.Batch(),
	func(e ecs.Entity, pos *Position, vel *Velocity) {
		pos.X, pos.Y = rand.Float64()*100, rand.Float64()*100
		vel.X, vel.Y = rand.NormFloat64(), rand.NormFloat64()
	})

API: Map2.AddBatchFn

🧩 Remove components from all entities matching a filter.

The callback can be used to do something with entities before component removal.

filter := ecs.NewFilter2[Position, Velocity](&world)

mapper.RemoveBatch(
	filter.Batch(),
	func(entity ecs.Entity) { /* ... */ })

API: Map2.RemoveBatch

Filters and queries

Queries are the main work horse for implementing logic.

🔍 Use filters and queries to iterate entities

Filters should be stored and re-used for best performance.
Always create a new query before iterating.

filter := ecs.NewFilter2[Position, Velocity](&world)

query := filter.Query()
for query.Next() {
	pos, vel := query.Get()
	pos.X += vel.X
	pos.Y += vel.Y
}

API: Filter1, Filter2, …, Filter2.Query, Query2.Next, Query2.Get

🔍 Filters can match additional components

For components the entities should have, but that are not accessed in the query.

filter := ecs.NewFilter2[Position, Velocity](&world).
	With(ecs.C[Altitude]())
_ = filter

API: Filter2.With

🔍 Filters can exclude components
filter := ecs.NewFilter2[Position, Velocity](&world).
	Without(ecs.C[Altitude]())
_ = filter

API: Filter2.Without

🔍 Filters can be exclusive on the given components

This filter matches only entities with exactly the given components.

filter := ecs.NewFilter2[Position, Velocity](&world).
	Exclusive()
_ = filter

API: Filter2.Exclusive

🔍 Filters can combine multiple conditions
filter := ecs.NewFilter1[Position](&world).
	With(ecs.C[Velocity]()).
	With(ecs.C[Altitude]()).
	Without(ecs.C[Health]())
_ = filter

API: Filter2.With, Filter2.Without

🔍 Access entities in query loops
query := filter.Query()
for query.Next() {
	entity := query.Entity()
	_ = entity
}

API: Query.Entity

🔍 Queries can count entities without iterating

Note that a query that is not iterated must be closed explicitly.

query := filter.Query()
fmt.Println(query.Count())

query.Close()

API: Query.Count, Query.Close

Access components

Components can also be accessed for arbitrary entities, not only inside queries.

🧩 A component mapper is required for component access outside queries

Allows access to the given components.

mapper := ecs.NewMap2[Position, Velocity](&world)
_ = mapper

API: Map1, Map2, …

🧩 Access components by entity
entity := mapper.NewEntity(&Position{X: 100, Y: 100}, &Velocity{X: 1, Y: -1})

pos, vel := mapper.Get(entity)
_, _ = pos, vel

API: Map2.Get

🧩 Check for existence of components for an entity
entity := mapper.NewEntity(&Position{X: 100, Y: 100}, &Velocity{X: 1, Y: -1})

hasPosAndVel := mapper.HasAll(entity)
_ = hasPosAndVel

API: Map2.HasAll

🧩 For single component access, there is a slightly more convenient mapper
mapper := ecs.NewMap[Position](&world)
entity := mapper.NewEntity(&Position{X: 100, Y: 100})

if mapper.Has(entity) {
	pos := mapper.Get(entity)
	_ = pos
}

API: Map

Resources

Resources are “global”, singleton-like data structures that are not associated to a particular entity.

📦 Adding and getting resources, the simple but slower way (≈20ns)
grid := NewGrid(100, 100)

ecs.AddResource(&world, &grid)
_ = ecs.GetResource[Grid](&world)

API: AddResource, GetResource

📦 For repeated access, better use a resource accessor (Get() ≈1ns)
gridResource := ecs.NewResource[Grid](&world)

grid := gridResource.Get()
_ = grid

(Creating the accessor does not add the actual Grid resource!)

API: Resource, Resource.Get

Events and observers

Observers allow to react to ECS lifecycle events, like entity creation or component addition.

👀 Create and register observers for ECS lifecycle events

Gets notified on any creation of an entity.

ecs.Observe(ecs.OnCreateEntity).
	Do(func(e ecs.Entity) {
		// Do something with the newly created entity.
	}).
	Register(&world)

API: Observer

👀 Observers can filter for certain components

Gets notified when a Position and a Velocity component are added to an entity.

ecs.Observe(ecs.OnAddComponents).
	For(ecs.C[Position]()).
	For(ecs.C[Velocity]()).
	Do(func(e ecs.Entity) {
		// Do something with the entity.
	}).
	Register(&world)

API: Observer.For

👀 Observers can process matched components

Gets notified when a Position and a Velocity component are added to an entity, with both available in the callback.

ecs.Observe2[Position, Velocity](ecs.OnAddComponents).
	Do(func(e ecs.Entity, pos *Position, vel *Velocity) {
		// Do something with the entity and the components
	}).
	Register(&world)

API: Observer1, Observer2, …

👀 Observers can also filter for the entity composition

Gets notified when a Position component is added to an entity that also has Velocity but not Altitude.

ecs.Observe1[Position](ecs.OnAddComponents).
	With(ecs.C[Velocity]()).
	Without(ecs.C[Altitude]()).
	Do(func(e ecs.Entity, pos *Position) {
		// Do something with the entity and the component
	}).
	Register(&world)

API: Observer1.With, Observer1.Without

📣 Custom event types can be created using a registry
var registry = ecs.EventRegistry{}

var OnCollisionDetected = registry.NewEventType()
var OnInputReceived = registry.NewEventType()

_, _ = OnCollisionDetected, OnInputReceived

API: EventRegistry, EventRegistry.NewEventType

📣 Custom events can be emitted by the user
event := world.Event(OnCollisionDetected).
	For(ecs.C[Position]())

entity := mapper.NewEntity(&Position{}, &Velocity{})
event.Emit(entity)

API: World.Event, Event

📣 Custom events can be observed like pre-defined events
ecs.Observe1[Position](OnCollisionDetected).
	Do(func(e ecs.Entity, p *Position) {
		// Do something with the collision entity and the component
	}).
	Register(&world)

API: Observer, Observer1, Observer2, …