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

Note

This feature is not yet released and is planned for Ark v0.6.0. You can try it out on the main branch.

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, …