### Run Ark Examples Source: https://github.com/mlange-42/ark/blob/main/examples/README.md Run most Ark examples from the examples directory using 'go run'. ```bash cd examples go run ./ ``` -------------------------------- ### Run Ebitengine Example Source: https://github.com/mlange-42/ark/blob/main/examples/README.md Run the Ebitengine example from its specific directory due to its dependencies. ```bash cd examples/ebitengine go run . ``` -------------------------------- ### Install Ark Go Package Source: https://github.com/mlange-42/ark/blob/main/docs/content/quickstart/index.md Use this command to add the Ark library to your Go project dependencies. ```bash go get github.com/mlange-42/ark ``` -------------------------------- ### Basic ECS Setup and Entity Iteration in Go Source: https://github.com/mlange-42/ark/blob/main/README.md Demonstrates setting up an ECS world, creating entities with Position and Velocity components, and iterating over them to update their positions based on velocity. Save mappers and filters permanently for best performance. ```go package main import ( "math/rand/v2" "github.com/mlange-42/ark/ecs" ) // Position component type Position struct { X, Y float64 } // Velocity component type Velocity struct { DX, DY float64 } func main() { // Create a new World world := ecs.NewWorld() // Create a component mapper // Save mappers permanently and re-use them for best performance mapper := ecs.NewMap2[Position, Velocity](world) // Create entities with components for range 1000 { _ = mapper.NewEntity( &Position{X: rand.Float64() * 100, Y: rand.Float64() * 100}, &Velocity{DX: rand.NormFloat64(), DY: rand.NormFloat64()}, ) } // Create a filter // Save filters permanently and re-use them for best performance filter := ecs.NewFilter2[Position, Velocity](world) // Time loop for range 5000 { // Get a fresh query and iterate it query := filter.Query() for query.Next() { // Component access through the Query pos, vel := query.Get() // Update component fields pos.X += vel.DX pos.Y += vel.DY } } } ``` -------------------------------- ### Add and Get Resources (Simple) Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Adds and retrieves resources using a straightforward but potentially slower method. Approximately 20ns per operation. ```go func TestResourcesQuick(t *testing.T) { var Grid Resource ecs.AddResource(&Grid) _ = ecs.GetResource(Grid.ID()) } ``` -------------------------------- ### Clone Ark Repository Source: https://github.com/mlange-42/ark/blob/main/examples/README.md Clone the Ark repository to access the examples. ```bash git clone https://github.com/mlange-42/ark cd ark ``` -------------------------------- ### ECS Position/Velocity Example Source: https://github.com/mlange-42/ark/blob/main/docs/content/quickstart/index.md A classical Position/Velocity example for Entity Component System (ECS) demonstration. This snippet is intended to showcase basic ECS functionality. ```go package main import ( "fmt" "github.com/mlange-42/ark/common" "github.com/mlange-42/ark/ecs" ) func main() { // Create a new ECS world. world := ecs.NewWorld(ecs.NewConfig().WithCapacity(1000, 1000)) // Define components. type Position struct { X, Y float64 } tntype Velocity struct { X, Y float64 } // Register components. posID := world.ComponentID[Position]() velID := world.ComponentID[Velocity]() // Create entities and add components. entity1 := world.NewEntity() world.Add(entity1, Position{X: 0, Y: 0}) world.Add(entity1, Velocity{X: 1, Y: 1}) entity2 := world.NewEntity() world.Add(entity2, Position{X: 10, Y: 10}) world.Add(entity2, Velocity{X: -1, Y: -1}) // Create a system to update positions based on velocities. system := func(w *ecs.World, dt float64) { query := w.Query(posID, velID) for query.Next() { pos := w.Get(query.Entity, posID).(*Position) vel := w.Get(query.Entity, velID).(*Velocity) pos.X += vel.X * dt pos.Y += vel.Y * dt } } // Run the system for a short duration. world.Tick(system, 0.1) // Print the updated positions. query := world.Query(posID) for query.Next() { pos := query.Entity.Get(posID).(*Position) fmt.Printf("Entity %d: Position(%.2f, %.2f)\n", query.Entity.ID(), pos.X, pos.Y) } } ``` -------------------------------- ### Ark Entity Relationships Example Source: https://github.com/mlange-42/ark/blob/main/docs/content/relations/index.md Demonstrates the use of Ark's entity relationships feature to represent animals of different species in multiple farms. This example uses the generic variant for specifying relationship targets. ```go package main import ( "fmt" "log" "github.com/mlange-42/ark/ecs" ) type Species struct { Name string } type Farm struct { Name string } func main() { // Initialize ECS secs := ecs.New( ecs.Component{Type: Species{}}, // Component for animal species ecs.Component{Type: Farm{}}, // Component for farm ) // Create entities for species _ = sec.NewEntity(Species{"Dog"}) catID := sec.NewEntity(Species{"Cat"}) birdID := sec.NewEntity(Species{"Bird"}) // Create entities for farms farm1ID := sec.NewEntity(Farm{"Farmhouse"}) farm2ID := sec.NewEntity(Farm{"Barn"}) // Define relationship types (using generic Rel for simplicity) // In a real application, you might use RelIdx for performance // or define custom relationship components. // For this example, we'll use a simple entity ID as the target. // Add animals to farms with species relationships // Dog in Farmhouse (Dog species, target is Dog entity) dog1 := sec.NewEntity(Species{"Dog"}) sec.AddRelation(dog1, dog1, 0) // Relation: dog1 is of species dog1 sec.AddRelation(dog1, farm1ID, 1) // Relation: dog1 is in farm1ID // Cat in Barn (Cat species, target is Cat entity) cat1 := sec.NewEntity(Species{"Cat"}) sec.AddRelation(cat1, cat1, 0) // Relation: cat1 is of species cat1 sec.AddRelation(cat1, farm2ID, 1) // Relation: cat1 is in farm2ID // Another Cat in Barn (Cat species, target is Cat entity) cat2 := sec.NewEntity(Species{"Cat"}) sec.AddRelation(cat2, cat2, 0) // Relation: cat2 is of species cat2 sec.AddRelation(cat2, farm2ID, 1) // Relation: cat2 is in farm2ID // Bird in Farmhouse (Bird species, target is Bird entity) bird1 := sec.NewEntity(Species{"Bird"}) sec.AddRelation(bird1, bird1, 0) // Relation: bird1 is of species bird1 sec.AddRelation(bird1, farm1ID, 1) // Relation: bird1 is in farm1ID // Query animals in Farmhouse and their species query := sec.Query(ecs.NewFilter().With(Farm{}), ecs.NewFilter().WithRelation(1, farm1ID)) for query.Next() { entity := query.Entity() var farm Farm var species Species sec.Get(entity, &farm) sec.GetRelation(entity, 0, &species) // Get species relation fmt.Printf("Entity %d: Farm '%s', Species '%s'\n", entity, farm.Name, species.Name) } // Query animals of species Cat and their farms queryCats := sec.Query(ecs.NewFilter().With(Species{}), ecs.NewFilter().WithRelation(0, catID)) fmt.Println("\nAnimals of species Cat:") for queryCats.Next() { entity := queryCats.Entity() var species Species var farm Farm sec.Get(entity, &species) sec.GetRelation(entity, 1, &farm) // Get farm relation fmt.Printf("Entity %d: Species '%s', Farm '%s'\n", entity, species.Name, farm.Name) } log.Println("Example finished.") } ``` -------------------------------- ### Get Component IDs Source: https://github.com/mlange-42/ark/blob/main/docs/content/unsafe/index.md Demonstrates how to obtain component IDs using the `ComponentID` function. If a component is not yet registered, it will be registered upon its first use. ```go func TestUnsafeIDs(t *testing.T) { world := ecs.NewWorld() ids := []ecs.ID{} for _, c := range []any{ &TestComponent1{}, &TestComponent2{}, &TestComponent3{}, } { id := world.ComponentID(c) ids = append(ids, id) } assert.Len(t, ids, 3) } ``` -------------------------------- ### Get Resource from World Source: https://github.com/mlange-42/ark/blob/main/docs/content/resources/index.md Retrieve a resource from the world by its type using `GetResource`. This method has an overhead of approximately 20ns for type lookup and is sufficient for one-time use. For regular access, Resource mappers are recommended. ```go func TestResourceWorld(t *testing.T) { world := ecs.NewWorld() defer world.Destroy(world) resource := &MyResource{Value: 10} world.AddResource(resource) retrieved := world.GetResource(ecs.TypeID(resource)) assert.Equal(t, resource, retrieved) } ``` -------------------------------- ### Process Matched Components in Observer Callback Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Gets notified when specific components are added to an entity, with both components available in the callback. Use this to access and process the added components directly. ```go func TestObserverFilterGeneric(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() var processed int world.Observer().Observe(&processed, func(e ecs.Entity, pos *Position, vel *Velocity) { processed++ assert.NotNil(t, pos) assert.NotNil(t, vel) }).For(&Position{}, &Velocity{}) world.NewEntity().Set(&Position{X: 1}).Set(&Velocity{X: 1}) assert.Equal(t, 1, processed) } ``` -------------------------------- ### Create and Register Observers for ECS Lifecycle Events Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Gets notified on any creation of an entity. Use this to perform actions whenever a new entity is added to the ECS. ```go func TestObserver(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() var created int world.Observer().Observe(&created, func(e ecs.Entity) { created++ }) world.NewEntity() assert.Equal(t, 1, created) } ``` -------------------------------- ### Query Entities with Dynamic Relation Targets Source: https://github.com/mlange-42/ark/blob/main/docs/content/relations/index.md When building a query, specify relation targets using `FilterX.Query` for short-lived targets. This avoids caching targets and allows the same filter to be used with different targets. Use `ecs.RelIdx` for performance when getting the query. ```go func TestFilter2(t *testing.T) { // ... setup code ... // Create entities with relations. entity1 := world.Map2.NewEntity(ecs.NewArchetype().Add(ecs.ChildOf{}), ecs.Rel(ecs.ChildOf{}), ecs.Entity(parent1)) entity2 := world.Map2.NewEntity(ecs.NewArchetype().Add(ecs.ChildOf{}), ecs.Rel(ecs.ChildOf{}), ecs.Entity(parent2)) entity3 := world.Map2.NewEntity(ecs.NewArchetype().Add(ecs.ChildOf{}), ecs.Rel(ecs.ChildOf{}), ecs.Entity(parent1)) // Build a filter for entities with a ChildOf relation. // No specific target is set here, making it a wildcard for targets. filter := ecs.Filter2.New().Relations(ecs.Rel(ecs.ChildOf{})) // Query the world, specifying the target dynamically. // `ecs.RelIdx(0)` refers to the first relation component in the archetype. // `ecs.Entity(parent1)` specifies the target entity for this query. query := world.Query(filter.Query(ecs.RelIdx(0), ecs.Entity(parent1))) // Assert that the query returns the correct entities. count := 0 for query.Next() { count++ if query.Entity() != entity1 && query.Entity() != entity3 { t.Fatalf("unexpected entity in query: %d", query.Entity()) } } query.Dispose() if count != 2 { t.Fatalf("expected 2 entities, got %d", count) } // Query again with a different target. query = world.Query(filter.Query(ecs.RelIdx(0), ecs.Entity(parent2))) count = 0 for query.Next() { count++ if query.Entity() != entity2 { t.Fatalf("unexpected entity in query: %d", query.Entity()) } } query.Dispose() if count != 1 { t.Fatalf("expected 1 entity, got %d", count) } } ``` -------------------------------- ### Filter Observers Based on Entity Composition (With/Without) Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Gets notified when a component is added to an entity that meets specific composition criteria (has certain components, lacks others). Use this for fine-grained control over when observers are triggered. ```go func TestObserverWithWithout(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() var processed int world.Observer().Observe(&processed, func(e ecs.Entity) { processed++ }).With(&Position{}).Without(&Altitude{}) // Entity with Position, but no Velocity or Altitude world.NewEntity().Set(&Position{}) assert.Equal(t, 1, processed) // Entity with Position and Velocity, but no Altitude world.NewEntity().Set(&Position{}).Set(&Velocity{}) assert.Equal(t, 2, processed) // Entity with Position and Altitude, should not be processed world.NewEntity().Set(&Position{}).Set(&Altitude{}) assert.Equal(t, 2, processed) } ``` -------------------------------- ### Extract Function Code Block Source: https://github.com/mlange-42/ark/blob/main/docs/layouts/shortcodes/code-func.html This shortcode extracts a specific function's code from a given file. It requires the file path, function name, and optionally start and end line numbers. The extracted code is then formatted as a Go code block. ```go {{ $file := .Get 0 }} {{ $func := .Get 1 }} {{ $startLine := 0 }} {{ $endLine := 9999 }} {{ with .Get 2 }}{{ if ne . "" }} {{ $startLine = int . }} {{ end }}{{ end }} {{ with .Get 3 }}{{ if ne . "" }} {{ $endLine = int . }} {{ end }}{{ end }} {{ $startPrefix := printf "func %s(" $func }} {{ $endPrefix := "}" }} {{ with .Page.Resources.Get $file }} {{ $s := .Content }} {{ $t := split $s "\n" }} {{ $code := "" }} {{ $started := false }} {{ $lineCount := 0 }} {{ range $t }} {{ if and ($started) (strings.HasPrefix . $endPrefix) }} {{ break }} {{ end }} {{ if $started }} {{ if and (ge $lineCount $startLine) (lt $lineCount $endLine) }} {{ $trim := strings.TrimPrefix "\t" . }} {{ $code = printf "%s%s\n" $code $trim }} {{ end }} {{ $lineCount = add $lineCount 1 }} {{ end }} {{ if and (not $started) (strings.HasPrefix . $startPrefix) }} {{ $started = true }} {{ end }} {{ end }} {{ $code := printf "\n`%s`\n" $code | markdownify }} {{ $code | safeHTML }} {{ end }} ``` -------------------------------- ### Get Relation Target Source: https://github.com/mlange-42/ark/blob/main/docs/content/relations/index.md Retrieve the target entity of a relation component using `MapX.GetRelation`. Note that due to Go's generic limitations, a generic way to get all relation targets is not directly available. ```go func TestGetRelation(t *testing.T) { // ... setup code ... // Create an entity with a ChildOf relation. entity := world.Map2.NewEntity(ecs.NewArchetype().Add(ecs.ChildOf{}), ecs.Rel(ecs.ChildOf{}), ecs.Entity(parent)) // Get the target of the ChildOf relation. target, ok := world.Map2.GetRelation(entity, ecs.ChildOf{}) if !ok { t.Fatal("entity missing ChildOf relation target") } // Assert that the target is the expected parent entity. if target != parent { t.Fatalf("expected parent %d, got %d", parent, target) } // Attempt to get a non-existent relation. _, ok = world.Map2.GetRelation(entity, ecs.SiblingOf{}) if ok { t.Fatal("entity should not have SiblingOf relation") } } ``` -------------------------------- ### Observe Any Entity Creation Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Shows how to set up an observer that triggers on the creation of any entity, regardless of its components. ```go func TestObserveCreateEmpty(t *testing.T) { world := ecs.NewWorld() world.Observe().Handle(func(id uint64) { assert.True(t, true) }) world.Spawn(&Position{}) world.Tick() } ``` -------------------------------- ### Serve Hugo Documentation Locally Source: https://github.com/mlange-42/ark/blob/main/docs/README.md Run this command from the root directory of the Ark project to build and serve the documentation site locally for development and preview. ```bash hugo serve ``` -------------------------------- ### Define and Use Resources in Go Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Demonstrates how to define and use resources in Go. Resources are singular data structures not associated with an entity. ```go func TestResource(t *testing.T) { world := NewWorld(t) // Create a resource world.AddResource(100) // Get a resource var tick int world.GetResource(&tick) assert.Equal(t, 100, tick) } ``` -------------------------------- ### Create a World with Initial Capacity Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Configure a world with an initial capacity for archetypes, entity lists, and other internal structures. This can improve performance by pre-allocating memory. ```go world := ecs.NewWorld(ecs.NewConfig().WithCapacity(10000)) ``` -------------------------------- ### Filter Observers for Specific Component Additions Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Gets notified when specific components are added to an entity. Use this to react only to entities that gain certain components. ```go func TestObserverFilter(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() var created int world.Observer().Observe(&created, func(e ecs.Entity) { created++ }).For((*Position)(nil), (*Velocity)(nil)) world.NewEntity() assert.Equal(t, 0, created) world.NewEntity().Set(&Position{}) assert.Equal(t, 0, created) world.NewEntity().Set(&Position{}).Set(&Velocity{}) assert.Equal(t, 1, created) } ``` -------------------------------- ### Observe Entity Creation Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Illustrates observing entity creation with and without direct component access in the callback. ```go func TestObserveCreate(t *testing.T) { world := ecs.NewWorld() world.Observe(&Position{}).Handle(func(id uint64) { assert.True(t, world.HasComponent(&Position{}, id)) }) world.Observe(&Position{}).Handle(func(id uint64) { assert.True(t, world.HasComponent(&Position{}, id)) }) world.Spawn(&Position{}) world.Tick() } ``` -------------------------------- ### Flexible Entity Creation with Callback Source: https://github.com/mlange-42/ark/blob/main/docs/content/batch/index.md Utilize `NewBatchFn` to create entities and apply a callback function for flexible initialization of each entity. This allows setting individual values during creation. ```go func TestNewBatchFn(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() batch := world.NewBatchFn(func(entity ecs.Entity) { entity.Set(Position{X: 10, Y: 20}) }) for i := 0; i < 1000; i++ { batch.Add(ecs.NewEntity()) } batch.Commit() assert.Equal(t, int64(1000), world.CountEntities()) world.Query(nil).Each(func(entity ecs.Entity) { pos := entity.Get(Position) assert.Equal(t, Position{X: 10, Y: 20}, pos) }) } ``` -------------------------------- ### Create and Use Resource Mapper Source: https://github.com/mlange-42/ark/blob/main/docs/content/resources/index.md Create a `Resource` mapper, store it, and use it for efficient resource retrieval. This method allows resource access in less than 1ns. Resource mappers can also be used to add, remove, and check for the existence of resources. ```go func TestResourceMapper(t *testing.T) { world := ecs.NewWorld() defer world.Destroy(world) mapper := ecs.NewResourceMapper[MyResource](world) resource := &MyResource{Value: 10} world.AddResource(resource) retrieved := mapper.Get() assert.Equal(t, resource, retrieved) } ``` ```go func TestResourceMapperAddRemove(t *testing.T) { world := ecs.NewWorld() defer world.Destroy(world) mapper := ecs.NewResourceMapper[MyResource](world) resource := &MyResource{Value: 10} world.AddResource(resource) assert.True(t, mapper.Has()) assert.Equal(t, resource, mapper.Get()) world.RemoveResource(ecs.TypeID(resource)) assert.False(t, mapper.Has()) } ``` -------------------------------- ### Basic Event Observation Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Demonstrates the fundamental usage of Ark's event system for observing entity creation. ```go func TestEventsBasic(t *testing.T) { world := ecs.NewWorld() world.Observe(&Position{}) world.Spawn(&Position{X: 1}) world.Tick() } ``` -------------------------------- ### Observe Creation of Entities with Specific Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Demonstrates observing the creation of entities that possess both 'Position' and 'Velocity' components. ```go func TestObserve2Create(t *testing.T) { world := ecs.NewWorld() world.Observe(&Position{}, &Velocity{}).Handle(func(id uint64) { assert.True(t, world.HasComponent(&Position{}, id)) assert.True(t, world.HasComponent(&Velocity{}, id)) }) world.Spawn(&Position{}, &Velocity{}) world.Tick() } ``` -------------------------------- ### Create Many Entities Using a Callback for Initialization Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates multiple entities efficiently, using a callback function for individual initialization of each entity's components. Suitable for bulk creation with varied component data. ```go entities := mapperB.NewBatchFn(100, func(b *ComponentB) { // Initialize component B here }) ``` -------------------------------- ### Add and Remove Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/unsafe/index.md Shows how to add and remove components from an entity using the methods provided by `Unsafe`. ```go func TestUnsafeComponents(t *testing.T) { world := ecs.NewWorld() id1 := world.ComponentID(&TestComponent1{}) id2 := world.ComponentID(&TestComponent2{}) entity := world.Unsafe.NewEntity(id1) assert.True(t, world.Unsafe.Has(entity, id1)) assert.False(t, world.Unsafe.Has(entity, id2)) world.Unsafe.Add(entity, id2) assert.True(t, world.Unsafe.Has(entity, id2)) world.Unsafe.Remove(entity, id1) assert.False(t, world.Unsafe.Has(entity, id1)) } ``` -------------------------------- ### Observe Component Addition Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Illustrates observing the addition of a 'Position' component to an existing entity. ```go func TestObserveAdd(t *testing.T) { world := ecs.NewWorld() var id uint64 id = world.Spawn(&Velocity{}) world.Observe(&Position{}).Handle(func(id uint64) { assert.True(t, world.HasComponent(&Position{}, id)) }) world.AddComponent(&Position{}, id) world.Tick() } ``` -------------------------------- ### Create Entities with Component IDs Source: https://github.com/mlange-42/ark/blob/main/docs/content/unsafe/index.md Shows how to create new entities using `Unsafe.NewEntity`, specifying the desired component IDs. ```go func TestUnsafeNewEntity(t *testing.T) { world := ecs.NewWorld() ids := []ecs.ID{ world.ComponentID(&TestComponent1{}), world.ComponentID(&TestComponent2{}), } entity := world.Unsafe.NewEntity(ids...) assert.True(t, world.Has(entity, ids[0])) assert.True(t, world.Has(entity, ids[1])) } ``` -------------------------------- ### Create a World with Default Settings Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Use NewWorld to create a world with default settings. This is the most basic way to initialize the ECS world. ```go world := ecs.NewWorld() ``` -------------------------------- ### Create Many Entities Efficiently with Same Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates multiple entities efficiently, all initialized with the same component values. Use for bulk creation of similar entities. ```go entities := mapperB.NewBatch(100, ComponentB{}) ``` -------------------------------- ### Create Entities in Batch Source: https://github.com/mlange-42/ark/blob/main/docs/content/batch/index.md Use `NewBatch` for creating a known number of similar entities efficiently. This is a common use case for batching. ```go func TestNewBatch(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() batch := world.NewBatch() for i := 0; i < 1000; i++ { batch.Add(ecs.NewEntity()) } batch.Commit() assert.Equal(t, int64(1000), world.CountEntities()) } ``` -------------------------------- ### Observe Component Addition with Conditions Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Demonstrates observing the addition of a 'Position' component to an entity that has 'Velocity' but not 'Altitude'. ```go func TestObserveAddWith(t *testing.T) { world := ecs.NewWorld() var id uint64 id = world.Spawn(&Velocity{}) world.Observe(&Position{}).With(&Velocity{}).Without(&Altitude{}).Handle(func(id uint64) { assert.True(t, world.HasComponent(&Position{}, id)) assert.True(t, world.HasComponent(&Velocity{}, id)) }) world.AddComponent(&Position{}, id) world.Tick() } ``` -------------------------------- ### Create a World with Specific Initial Capacity Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates a new world with a specified initial capacity for ECS data storage. Configure initial capacity based on expected usage. ```go world := ecs.New(ecs.WithCapacity(1000)) ``` -------------------------------- ### Create an Entity with a Component Mapper Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Demonstrates the requirement of a component mapper for creating entities with components. Component mappers should be stored and reused for performance. ```go type ComponentA struct {} type ComponentB struct {} mapperA := ecs.Map1[ComponentA]{} mapperB := ecs.Map2[ComponentB]{} ``` -------------------------------- ### Create a Single Entity Using a Callback Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates a single entity and initializes its components using a callback function for more complex initialization logic. ```go entity := mapperB.NewEntityFn(func(b *ComponentB) { // Initialize component B here }) ``` -------------------------------- ### Create a Single Entity with Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates a single entity and initializes it with specified component values using a component mapper. ```go entity := mapperB.NewEntity(ComponentB{}) ``` -------------------------------- ### Run Code Generation Source: https://github.com/mlange-42/ark/blob/main/ecs/internal/generate/README.md Execute the code generation process for the entire project. This command should be run from the project's root directory. ```bash go generate ./... ``` -------------------------------- ### Create New Entity with Relations Source: https://github.com/mlange-42/ark/blob/main/docs/content/relations/index.md When creating new entities, use `MapX.NewEntity` with `ecs.Rel` or `ecs.RelIdx` to specify relation targets. `ecs.RelIdx` is faster but requires the correct zero-based index of the relation component. ```go func TestNewEntity(t *testing.T) { // ... setup code ... // Create a new entity with a relation to another entity. // The relation is of type ChildOf and targets entity `parent`. entity := world.Map2.NewEntity( ecs.NewArchetype().Add(ecs.ChildOf{}), ecs.Rel(ecs.ChildOf{}), ecs.Entity(parent), ) // Assert that the entity was created and has the relation. if !world.Has(entity, ecs.ChildOf{}) { t.Fatal("entity missing ChildOf relation") } // Get the target of the ChildOf relation. target, ok := world.Map2.GetRelation(entity, ecs.ChildOf{}) if !ok { t.Fatal("entity missing ChildOf relation target") } // Assert that the target is the expected parent entity. if target != parent { t.Fatalf("expected parent %d, got %d", parent, target) } } ``` -------------------------------- ### Create a World with Default Initial Capacity Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates a new world with default initial capacity. This is the central ECS data storage. ```go world := ecs.New() ``` -------------------------------- ### Enable Debug Build for Informative Errors Source: https://github.com/mlange-42/ark/blob/main/docs/content/errors/index.md Run your project with the `ark_debug` build tag to enable additional checks for more helpful error messages. This may incur a performance penalty. ```bash go run -tags ark_debug . ``` -------------------------------- ### Iterate Entities with Filters and Queries Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Use filters to define entity criteria and then create a query to iterate over matching entities. Always create a new query before iterating. ```go func TestFilterQuery(t *testing.T) { filter := ecs.Filter1{} query := ecs.Filter2{}.Query(filter) for query.Next() { query.Get() } } ``` -------------------------------- ### Add Resource to World Source: https://github.com/mlange-42/ark/blob/main/docs/content/resources/index.md Instantiate a resource struct and add a pointer to it to the world using `AddResource`, typically during world initialization. The original resource struct can be modified, and changes will be reflected when retrieving the resource. ```go func TestAddResource(t *testing.T) { world := ecs.NewWorld() defer world.Destroy(world) resource := &MyResource{Value: 10} world.AddResource(resource) retrieved := world.GetResource(ecs.TypeID(resource)) assert.Equal(t, resource, retrieved) } ``` -------------------------------- ### Define Custom Event Types Using Iota Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Alternatively, define custom event types using constants and iota if all custom events are declared in a single place. ```go func TestNewEventTypeIota() { var ( // Define custom event types using iota for sequential numbering. Created = ecs.NewEventType() Removed = ecs.NewEventType() ) _ = Created _ = Removed } ``` -------------------------------- ### Create a Query with Component Filters Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Create a query that iterates over entities possessing specific component types. Filters are used for performance, so create them once and reuse. ```go filter := ecs.NewFilter(ecs.ComponentID(&Position{}), ecs.ComponentID(&Velocity{})) query := world.Query(filter) for query.Next() { pos := query.Get(&Position{}).(*Position) vel := query.Get(&Velocity{}).(*Velocity) _ = pos _ = vel } ``` -------------------------------- ### World Lock during Query Source: https://github.com/mlange-42/ark/blob/main/docs/content/queries/index.md Demonstrates handling the world lock acquired during query creation. Entities are collected during iteration and operations are performed afterwards to avoid deadlocks. ```go func TestQueriesLock(t *testing.T) { world := ecs.NewWorld() posID := world.ComponentID[Position](nil) velID := world.ComponentID[Velocity](nil) entity1 := world.Spawn(posID, velID) entity2 := world.Spawn(posID) query := world.Query(posID) var collected []ecs.Entity for query.Next() { entity := query.Entity() collected = append(collected, entity) if entity == entity1 { world.Remove(entity, posID) } } query.Close() world.Spawn(velID) fmt.Printf("Collected %d entities\n", len(collected)) } ``` -------------------------------- ### Combining Multiple Event Types Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Shows how to combine observers to react to multiple event types, such as component addition and removal, within a single callback. ```go func TestCombineObservers(t *testing.T) { world := ecs.NewWorld() var count int world.Observe(&Position{}, &Velocity{}).Handle(func(id uint64) { count++ }) world.Spawn(&Position{X: 1}, &Velocity{X: 1}) world.Tick() world.RemoveComponent(&Position{}, id) world.Tick() world.RemoveComponent(&Velocity{}, id) world.Tick() assert.Equal(t, 3, count) } ``` -------------------------------- ### Process and Display Go Code Snippet Source: https://github.com/mlange-42/ark/blob/main/docs/layouts/shortcodes/code.html This shortcode processes a Go file, performs string replacements to clean up testing imports and formatting, and then displays the modified code. It allows for an optional second argument to specify a different main function name. ```go {{ $file := .Get 0 }} {{ $main := "TestMain" }} {{ if .Get 1 }} {{ $main = .Get 1 }} {{ end }} {{ with .Page.Resources.Get $file }} {{ $s := .Content }} {{ $s = replace $s "(\r\n\t\"testing\"\r\n\r\n" "(\r\n" }} {{ $s = replace $s "\t\"testing\"\r\n" "" }} {{ $s = replace $s "(\n\t\"testing\"\n\n" "(\n" }} {{ $s = replace $s "\t\"testing\"\n" "" }} {{ $s = replace $s "\r\n\r\nimport \"testing\"" "" }} {{ $s = replace $s "\n\nimport \"testing\"" "" }} {{ $func := printf "func %s(t *testing.T) {" $main }} {{ $s = replace $s $func "func main() {" }} {{ $code := printf "\n`%s`\n" $s | markdownify }} {{ $code | safeHTML }} {{ end }} ``` -------------------------------- ### Add and remove components to/from a single entity Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md This snippet demonstrates how to add and remove specific components from an individual entity using its mapper. ```go func TestAddRemoveComponents(t *testing.T) { world := ecs.NewWorld() mapper := ecs.Map2[Component2](world) entity := world.NewEntity() mapper.Add(entity, Component2{Value: 10}) mapper.Remove(entity) } ``` -------------------------------- ### Create Custom Event Types Using a Registry Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Allows the creation of unique event types that can be emitted and observed within the ECS. Use this to define domain-specific events. ```go func TestCustomEventType(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() eventRegistry := world.EventRegistry() var MyEvent = eventRegistry.NewEventType(func() interface{} { return &struct{ Value int }{} }) var notified int world.Observer().Observe(¬ified, func(e ecs.Entity) { notified++ }).For(MyEvent) world.Event(MyEvent).Commit(&struct{ Value int }{Value: 100}) assert.Equal(t, 1, notified) } ``` -------------------------------- ### Access Components by ID Source: https://github.com/mlange-42/ark/blob/main/docs/content/unsafe/index.md Demonstrates accessing component data for an entity using `Unsafe.Get` and checking for component existence with `Unsafe.Has`. Similar to querying, `Unsafe.Get` returns an `unsafe.Pointer` requiring a type cast. ```go func TestUnsafeGet(t *testing.T) { world := ecs.NewWorld() id := world.ComponentID(&TestComponent1{}) entity := world.Unsafe.NewEntity(id) var c *TestComponent1 assert.True(t, world.Unsafe.Get(entity, id, &c)) assert.NotNil(t, c) assert.True(t, world.Unsafe.Has(entity, id)) } ``` -------------------------------- ### Resource Accessor for Repeated Access Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Provides a faster way to access resources for repeated use, with approximately 1ns per operation. Creating the accessor does not add the resource itself. ```go func TestResources(t *testing.T) { accessor := ecs.Resource.NewAccessor() _ = accessor.Get() } ``` -------------------------------- ### Add components to all entities matching a filter with individual initialization Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md This function adds components to entities matching a filter, allowing for individual initialization of each component via a callback function. ```go func TestAddBatchFn(t *testing.T) { world := ecs.NewWorld() mapper := ecs.Map2[Component2](world) entity1 := world.NewEntity() entity2 := world.NewEntity() world.Filter().WithAll(ecs.AnyComponent).AddFn(mapper, func(entity ecs.Entity) Component2 { return Component2{Value: 10} }) } ``` -------------------------------- ### Create Component Mapper Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Allows access to components for arbitrary entities. A component mapper is required for component access outside of queries. ```go func TestCreateMapper(t *testing.T) { mapper := ecs.Map1.NewMapper() _ = mapper } ``` -------------------------------- ### Create a component mapper Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md A component mapper is required for adding and removing components efficiently. Mappers should be stored and reused. ```go type ComponentMapper1 struct { mapper *ecs.Mapper[Component1] } type ComponentMapper2 struct { mapper *ecs.Mapper[Component2] } func TestCreateMapper(t *testing.T) { world := ecs.NewWorld() mapper1 := ecs.Map1[Component1](world) mapper2 := ecs.Map2[Component2](world) _ = ComponentMapper1{mapper: mapper1} _ = ComponentMapper2{mapper: mapper2} } ``` -------------------------------- ### Create an Entity Without Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Creates a new entity without any components. Entities represent objects in a simulation. ```go entity := world.NewEntity() ``` -------------------------------- ### Create and Use Component Mappers Source: https://github.com/mlange-42/ark/blob/main/docs/content/operations/index.md Use component mappers to create entities with components, add components to entities, and remove components from entities. Mappers are parametrized by the component types they handle. The number in the mapper creation function (e.g., NewMap2) denotes the number of mapped components. ```go func TestComponentMapper(t *testing.T) { var ( mapper = ecs.NewMap2[Position, Velocity]() ) entity := ecs.NewEntity() mapper.Add(entity, Position{X: 10, Y: 20}, Velocity{X: 1, Y: 1}) mapper.Add(entity, Position{X: 15, Y: 25}, Velocity{X: 2, Y: 2}) mapper.Remove(entity) } ``` -------------------------------- ### Table-based Iteration Source: https://github.com/mlange-42/ark/blob/main/docs/content/queries/index.md Iterates over entire tables/archetypes and provides access to component columns for faster iteration. Requires an inner loop over entities. ```go func TestQueriesTables(t *testing.T) { world := ecs.NewWorld() posID := world.ComponentID[Position](nil) velID := world.ComponentID[Velocity](nil) world.Spawn(posID, velID) world.Spawn(posID) query := world.Query(posID, velID) for query.NextTable() { _ = query.GetColumns(posID).(*[]Position) _ = query.GetColumns(velID).(*[]Velocity) fmt.Printf("Table with %d entities\n", query.Len()) for i := 0; i < query.Len(); i++ { entity := query.Entity(i) fmt.Printf(" Entity %d\n", entity) } } } ``` -------------------------------- ### Filter Entities with Additional Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Use the 'With' method to specify additional components that entities must possess, even if they are not directly accessed in the query. This allows for more precise filtering. ```go func TestFilterWith(t *testing.T) { filter := ecs.Filter2{}.With() query := filter.Query() for query.Next() { query.Get() } } ``` -------------------------------- ### Query Entities by Component IDs Source: https://github.com/mlange-42/ark/blob/main/docs/content/unsafe/index.md Illustrates how to use filters and queries with component IDs instead of generics. Note the required type casts when accessing component data via `UnsafeQuery.Get`, as it returns an `unsafe.Pointer`. ```go func TestUnsafeQuery(t *testing.T) { world := ecs.NewWorld() ids := []ecs.ID{ world.ComponentID(&TestComponent1{}), world.ComponentID(&TestComponent2{}), } world.Unsafe.NewEntity(ids...) query := world.Unsafe.Query(ids...) for query.Next() { var c1 *TestComponent1 var c2 *TestComponent2 query.Get(&c1, &c2) assert.NotNil(t, c1) assert.NotNil(t, c2) } } ``` -------------------------------- ### Emit and Observe a Custom Event Source: https://github.com/mlange-42/ark/blob/main/docs/content/events/index.md Emit a custom event using `world.Events().NewEvent(EventType).SetComponent(component).Submit()`. Observers can then react to this event. ```go func TestEventEmit() { world := ecs.NewWorld() defer world.Destroy() // Define a custom event type. var Clicked = ecs.NewEventType() // Register an observer for the Clicked event. world.Observer().On(ecs.Type2[Position](), func(e ecs.Entity) { // Observer logic here. }).With(ecs.Type2[Position]()).Listen(Clicked) // Emit the Clicked event with a Position component. world.Events().NewEvent(Clicked).SetComponent(Position{1, 2}).Submit() } ``` -------------------------------- ### Handle Optional Data with Map Methods Source: https://github.com/mlange-42/ark/blob/main/docs/content/queries/index.md Instead of using Optional, use Map.Has and Map.Get for checking and retrieving optional data. This avoids additional checks in query functions. ```go func TestQueriesOptional(t *testing.T) { world := ecs.NewWorld(ecs.NewConfig()) world.Add( "test", Map2.New( "key1", 1, "key2", 2, ), ) query := ecs.NewQuery2(world, "test") query.Each(func(id string, m Map2) { assert.True(t, m.Has("key1")) assert.False(t, m.Has("key3")) val1, ok1 := m.Get("key1") // val1 is interface{}, ok1 is bool assert.True(t, ok1) assert.Equal(t, 1, val1) val3, ok3 := m.Get("key3") assert.False(t, ok3) assert.Nil(t, val3) }) } ``` -------------------------------- ### Test World Statistics Source: https://github.com/mlange-42/ark/blob/main/docs/content/stats/index.md Demonstrates how to access and print world statistics, including component counts, archetype information, memory usage, and entity pool details. This function is part of the stats test suite. ```go func TestWorldStats(t *testing.T) { stats := WorldStats(t) fmt.Println(stats.String()) } ``` -------------------------------- ### Create an Entity Without Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Create a new entity in the world. Entities are opaque IDs used to access components. ```go entity := world.NewEntity() ``` -------------------------------- ### Filter with Multiple 'With' Calls Source: https://github.com/mlange-42/ark/blob/main/docs/content/queries/index.md Demonstrates that the 'With' method can be called multiple times to specify additional required components for the query. ```go func TestQueriesWith2(t *testing.T) { world := ecs.NewWorld() posID := world.ComponentID[Position](nil) velID := world.ComponentID[Velocity](nil) altID := world.ComponentID[Altitude](nil) world.Spawn(posID, velID, altID) world.Spawn(posID, velID) query := world.Query(posID).With(velID).With(altID) for query.Next() { fmt.Printf("Entity %d has Position, Velocity and Altitude\n", query.Entity()) } } ``` -------------------------------- ### Efficiently Exchange Components Source: https://github.com/mlange-42/ark/blob/main/docs/content/operations/index.md Perform component additions and removals in a single operation for efficiency, as these are costly operations. Use Exchange functions to add specified components and remove others simultaneously. ```go func TestExchange(t *testing.T) { var ( mapper = ecs.NewMap2[Position, Velocity]() ) entity := ecs.NewEntity() mapper.Add(entity, Position{X: 10, Y: 20}, Velocity{X: 1, Y: 1}) mapper.Exchange(entity, []any{Position{X: 15, Y: 25}, Velocity{X: 2, Y: 2}}, []any{Position{X: 10, Y: 20}, Velocity{X: 1, Y: 1}}, ) pos := mapper.Get(entity) assert.Equal(t, &Position{X: 15, Y: 25}, pos) } ``` -------------------------------- ### Observe Custom Events Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Enables observers to react to custom-defined event types, similar to how they react to pre-defined ECS lifecycle events. Use this for unified event handling. ```go func TestCustomEventObserver(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() var MyEvent = world.EventRegistry().NewEventType(func() interface{} { return &struct{ Value int }{} }) var notified int world.Observer().Observe(¬ified, func(e ecs.Entity) { notified++ }).For(MyEvent) world.Event(MyEvent).Commit(&struct{ Value int }{Value: 100}) assert.Equal(t, 1, notified) } ``` -------------------------------- ### Access Components by Entity Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Access components associated with a specific entity using a component mapper. ```go func TestComponentAccess(t *testing.T) { mapper := ecs.Map2.NewMapper() var component Component _ = mapper.Get(Entity{}, &component) } ``` -------------------------------- ### Fast Table-Based Entity Iteration Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Achieve faster iteration using table-based access, which is twice as fast as standard iteration. This method is suitable for scenarios requiring high-performance data retrieval. ```go func TestFilterQueryTable(t *testing.T) { filter := ecs.Filter1{} query := ecs.Filter2{}.Query(filter) for query.NextTable() { query.GetColumns() } } ``` -------------------------------- ### Basic Query with Position and Velocity Source: https://github.com/mlange-42/ark/blob/main/docs/content/queries/index.md This filter matches entities that have both Position and Velocity components, and potentially others like Altitude. ```go func TestQueriesBasic(t *testing.T) { world := ecs.NewWorld() posID := world.ComponentID[Position](nil) velID := world.ComponentID[Velocity](nil) world.Spawn(posID, velID) world.Spawn(posID) query := world.Query(posID, velID) for query.Next() { entity := query.Entity() _ = query.Get(posID).(*Position) _ = query.Get(velID).(*Velocity) fmt.Printf("Entity %d has Position and Velocity\n", entity) } } ``` -------------------------------- ### Check for Component Existence Source: https://github.com/mlange-42/ark/blob/main/docs/content/cheatsheet/index.md Verify if an entity possesses all specified components using a component mapper. ```go func TestComponentCheck(t *testing.T) { mapper := ecs.Map2.NewMapper() _ = mapper.HasAll(Entity{}, Component1{}, Component2{}) } ``` -------------------------------- ### Reset a Populated World for Reuse Source: https://github.com/mlange-42/ark/blob/main/docs/content/concepts/index.md Reset a world to clear all entities and components, making it ready for reuse. This is useful for simulations that require multiple runs. ```go world.Reset() ``` -------------------------------- ### Batch Component Operations Source: https://github.com/mlange-42/ark/blob/main/docs/content/batch/index.md Manage components in batches using `Map2` and `Exchange2` with a `Batch` filter to specify affected entities. This is useful for adding, removing, or exchanging components across multiple entities efficiently. ```go func TestBatchComponents(t *testing.T) { world := ecs.NewWorld() defer world.Destroy() for i := 0; i < 1000; i++ { entity := world.NewEntity() entity.Set(Position{X: float32(i), Y: float32(i)}) if i%2 == 0 { entity.Set(Velocity{X: float32(i), Y: float32(i)}) } } batch := world.Batch() batch.Remove(ecs.NewType(Velocity{})) batch.Commit() assert.Equal(t, int64(1000), world.CountEntities()) assert.Equal(t, int64(0), world.CountComponents(ecs.NewType(Velocity{}))) assert.Equal(t, int64(1000), world.CountComponents(ecs.NewType(Position{}))) } ```