volt

package module
v1.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 17, 2025 License: Apache-2.0 Imports: 6 Imported by: 13

README

Volt

GitHub go.mod Go version License Go Reference Go Report Card Tests Codecov GitHub Issues or Pull Requests GitHub Issues or Pull Requests

Volt is an ECS(entity-component-system) oriented for games development with Go. It is inspired by the documentation available here: https://github.com/SanderMertens/ecs-faq

There is many ways to write an ECS, and Volt is based on the Archetype paradigm.

Knowledge

Entity

An entity is the end object in a game (e.g. a character). It is only defined by its identifier called EntityId. This identifier is generated, its type uint64 avoiding to generate twice the same id. When an entity is removed, this identifier can be used again for a new one.

Looking at the benchmark, a scene can handle between 100.000 to 1.000.000 depending on your machine and the complexity of the project. But of course, the lower the better, as it will allow the project to run on slower computers.

Component

An entity is composed from 1 to N Component(s). It is a structure of properties, and should not contain any logic by itself (meaning no functions). The Components are manipulated by Systems.

A Component is defined by its ComponentId, ranging between [0;2048].

System

A system is a specialized tool that fetches entities, filtered by their Components, and transforms the datas. For example: the audio could be managed by a system, or the graphics managed by a render system.

Volt does not directly implements Systems, but allows you to create Queries that you can use in your own specific tools.

Query

A Query is a search tool for the set of entities that possess (at least) the list of ComponentId provided. It is then possible to iterate over the result of this search within a System, in order to manipulate the Components.

Archetype

In an ECS (Entity-Component-System), an Archetype is the set of Entities that share the same ComponentId. The Archetype itself is not publicly exposed, but is instead managed internally and represents a major structure within Volt.

Storage

Using the Structure Of Arrays (SoA) paradigm, Components are persisted in a dedicated storage for each ComponentId. This allows for cache hits during read phases within Query iterations, resulting in significantly improved performance compared to an Array of Structures (AoS) model.

Basic Usage

  • Create a World to contain all the datas
world := volt.CreateWorld()
  • Create your components, and implement the ComponentInterface with GetComponentId(). Your ComponentId should range between [0;2048].
const (
    transformComponentId = iota
)

type transformComponent struct {
x, y, z float64
}

func (t transformComponent) GetComponentId() volt.ComponentId {
    return transformComponentId
}
type transformConfiguration struct {
	x, y, z float64
}
  • Register the component for the world to use it. The BuilderFn is optional, it allows to initialize and customize the component at its creation.
volt.RegisterComponent[transformComponent](world, &ComponentConfig[transformComponent]{BuilderFn: func(component any, configuration any) {
    conf := configuration.(*transformConfiguration)
    transformComponent := component.(*transformComponent)
	transformComponent.x = conf.x
	transformComponent.y = conf.y
	transformComponent.z = conf.z
}})
  • Create the entity
entityId := world.CreateEntity()

Important: the entity will receive a unique identifier. When the entity is removed, this id can be used again and assigned to a new entity.

  • Add the component to the entity
component := volt.ConfigureComponent[transformComponent](&world, transformConfiguration{x: 1.0, y: 2.0, z: 3.0})
volt.AddComponent(&world, entity, component)
  • Remove the component to the entity
err := RemoveComponent[testTransform](world, entityId)
if err != nil {
	fmt.Println(err)
}
  • Delete the entity
world.RemoveEntity(entityId)

Queries

The most powerful feature is the possibility to query entities with a given set of Components. For example, in the Rendering system of the game engine, a query will fetch only for the entities having a Mesh & Transform:

query := volt.CreateQuery2[transformComponent, meshComponent](world, volt.QueryConfiguration{OptionalComponents: []volt.OptionalComponent{meshComponentId}})
for result := range query.Foreach(nil) {
    transformData(result.A)
}

DEPRECATED: The Foreach function receives a function to pre-filter the results, and returns an iterator.

For faster performances, you can use concurrency with the function ForeachChannel:

query := volt.CreateQuery2[transformComponent, meshComponent](world, volt.QueryConfiguration{OptionalComponents: []volt.OptionalComponent{meshComponentId}})
queryChannel := query.ForeachChannel(1000, nil)

runWorkers(4, func(workerId int) {
    for results := range queryChannel {
        for result := range results {
            transformData(result.A)
        }
    }
})

func runWorkers(workersNumber int, worker func(int)) {
  var wg sync.WaitGroup
  
  for i := range workersNumber {
    i := i
    wg.Add(1)
    go func(worker func(int)) {
        defer wg.Done()
        worker(i)
    }(worker)
  }
  
  wg.Wait()
}

The Task function replaces the previous ForeachChannel function: it gives a simpler API with smaller memory footprint, for similar performances.

It is useful for executing code on all queried entities, by dispatching the work across a defined number of workers. Internally, this function leverages the iterator typically obtained through the Foreach function.

The number of workers should be considered based on the task's complexity: dispatching has a minmal overhead that might outweigh the benefits if the entity count to worker count ratio is not appropriate.

query := volt.CreateQuery2[transformComponent, meshComponent](world, volt.QueryConfiguration{OptionalComponents: []volt.OptionalComponent{meshComponentId}})

query.Task(4, nil, func(result volt.QueryResult2[transformComponent, meshComponent]) {
    transformData(result.A)
})

Queries exist for 1 to 8 Components.

You can also get the number of entities, without looping on each:

total := query.Count()

Or get the entities identifiers as a slice:

entitiesIds := query.FetchAll()

Tags

Tags are considered like any other Component internally, except they have no structure/value attached. They cannot be fetched using functions like GetComponent. Due to their simpler form, they do not need to be registered.

Tags are useful to categorize your entities.

e.g. "NPC", "STATIC", "DISABLED". For example, if you want to fetch only static content, you can query through the tag "STATIC". The Query will return only the entities tagged, in a faster way than applying the filter function in Query.Foreach to check on each entities if they are static.

e.g. to fetch only static entities:

const TAG_STATIC_ID = iota + volt.TAGS_INDICES
query := volt.CreateQuery2[transformComponent, meshComponent](world, volt.QueryConfiguration{Tags: []volt.TagId{TAG_STATIC_ID}})
for result := range query.Foreach(nil) {
    transformData(result.A)
}

Important: the TagIds should start from volt.TAGS_INDICES, allowing a range from [2048; 65535] for TagIds.

You can Add a Tag, check if an entity Has a Tag, or Remove it:

world.AddTag(TAG_STATIC_ID, entityId)
world.HasTag(TAG_STATIC_ID, entityId)
world.RemoveTag(TAG_STATIC_ID, entityId)

Events

The lifecycle (creation/deletion) of entities and components can trigger events. You can configure a callback function for each of these events, to execute your custom code:

world := volt.CreateWorld(100)
world.SetEntityAddedFn(func(entityId volt.EntityId) {
    fmt.Println("A new entity has been created", entityId)
})
world.SetEntityRemovedFn(func(entityId volt.EntityId) {
    fmt.Println("An entity has been deleted", entityId)
})
world.SetComponentAddedFn(func(entityId volt.EntityId, componentId volt.ComponentId) {
    fmt.Println("The component", componentId, "is attached to the entity", entityId)
})
world.SetComponentRemovedFn(func(entityId volt.EntityId, componentId volt.ComponentId) {
fmt.Println("The component", componentId, "is removed from the entity", entityId)
})

Naming entities

Volt managed the naming of entities up to the version 1.6.0. For performances reasons, this feature is removed from the v1.7.0+. You now have to keep track of the names by yourself in your application:

  • Having a simple map[name string]volt.EntityId, you can react to the events and register these. Keep in mind that if your scene has a lot of entities, it will probably have a huge impact on the garbage collector.
  • Add a MetadataComponent. To fetch an entity by its name can be very slow, so you probably do not want to name all your entities. For example:
const MetadataComponentId = 0

type MetadataComponent struct {
	Name string
}

func (MetadataComponent MetadataComponent) GetComponentId() volt.ComponentId {
	return MetadataComponentId
}
volt.RegisterComponent[MetadataComponent](&world, &volt.ComponentConfig[MetadataComponent]{BuilderFn: func(component any, configuration any) {}})

func GetEntityName(world *volt.World, entityId volt.EntityId) string {
    if world.HasComponents(entityId, MetadataComponentId) {
        metadata := volt.GetComponent[MetadataComponent](world, entityId)
    
        return metadata.Name
    }
    
    return ""
}

func (scene *Scene) SearchEntity(name string) volt.EntityId {
    q := volt.CreateQuery1[MetadataComponent](&world, volt.QueryConfiguration{})
    for result := range q.Foreach(nil) {
        if result.A.Name == name {
            return result.EntityId
        }
    }
    
    return 0
}

Benchmark

Few ECS tools exist for Go. Arche and unitoftime/ecs are probably the most looked at, and the most optimized. In the benchmark folder, this module is compared to both of them.

The given results were produced by a ryzen 7 5800x, with 100.000 entities:

goos: linux goarch: amd64 pkg: benchmark cpu: AMD Ryzen 7 5800X 8-Core Processor

Benchmark Iterations ns/op B/op Allocs/op
BenchmarkCreateEntityArche-16 171 7138387 11096954 61
BenchmarkIterateArche-16 2798 429744 354 4
BenchmarkAddArche-16 253 4673362 122153 100000
BenchmarkRemoveArche-16 247 4840772 100000 100000
BenchmarkCreateEntityUECS-16 27 38852089 49119503 200146
BenchmarkIterateUECS-16 4892 235333 128 3
BenchmarkAddUECS-16 28 38982533 4721942 100005
BenchmarkRemoveUECS-16 30 40290316 3336712 100000
BenchmarkCreateEntityVolt-16 63 18836136 35181458 100101
BenchmarkIterateVolt-16 3619 337764 256 8
(DEPRECATED) BenchmarkIterateConcurrentlyVolt-16 9164 121653 3324 91
BenchmarkTaskVolt-16 9859 119525 1847 38
BenchmarkAddVolt-16 103 11379690 4313182 300000
BenchmarkRemoveVolt-16 146 7647252 400001 100000

These results show a few things:

  • Arche is the fastest tool for writes operations. In our game development though we would rather lean towards fastest read operations, because the games loops will read way more often than write.
  • Unitoftime/ecs is the fastest tool for read operations on one thread only, but the writes are currently way slower than Arche and Volt (except on the Create benchmark).
  • Volt is a good compromise, an in-between: fast enough add/remove operations, and almost as fast as Arche and UECS for reads on one thread. Volt uses the new iterators from go1.23, which in their current implementation are slower than using a function call in the for-loop inside the Query (as done in UECS). This means, if the Go team finds a way to improve the performances from the iterators, we can hope to acheive near performances as UECS.
  • Thanks to the iterators, Volt provides a simple way to use goroutines for read operations. The data is received through a channel of iterator. As seen in the results, though not totally comparable, this allows way faster reading operations than any other implementation, and to use all the CPU capabilities to perform hard work on the components.
  • It might be doable to use goroutines in Arche and UECS, but I could not find this feature natively? Creating chunks of the resulted slices would generate a lot of memory allocations and is not desirable.
Other benchmarks

The creator and maintainer of Arche has published more complex benchmarks available here: https://github.com/mlange-42/go-ecs-benchmarks

What is to come next ?

  • For now the system is not designed to manage writes on a concurrent way: it means it is not safe to add/remove components in queries using multiples threads/goroutines. I need to figure out how to implement this, though I never met the need for this feature myself.

Sources

Contributing Guidelines

See how to contribute.

Licence

This project is distributed under the Apache 2.0 licence.

Documentation

Overview

Package volt is an ECS for game development, based on the Archetype paradigm.

Index

Constants

View Source
const COMPONENTS_INDICES = 0
View Source
const TAGS_INDICES = 2048

Variables

This section is empty.

Functions

func AddComponent

func AddComponent[T ComponentInterface](world *World, entityId EntityId, component T) error

AddComponent adds the component T to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has the component
  • an internal error occurs

func AddComponents2

func AddComponents2[A, B ComponentInterface](world *World, entityId EntityId, a A, b B) error

AddComponents2 adds the components A, B to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents3

func AddComponents3[A, B, C ComponentInterface](world *World, entityId EntityId, a A, b B, c C) error

AddComponents3 adds the components A, B, C to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents4

func AddComponents4[A, B, C, D ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D) error

AddComponents4 adds the components A, B, C, D to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents5

func AddComponents5[A, B, C, D, E ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E) error

AddComponents5 adds the components A, B, C, D, E to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents6

func AddComponents6[A, B, C, D, E, F ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F) error

AddComponents6 adds the components A, B, C, D, E, F to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents7

func AddComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F, g G) error

AddComponents7 adds the components A, B, C, D, E, F, G to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func AddComponents8

func AddComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, entityId EntityId, a A, b B, c C, d D, e E, f F, g G, h H) error

AddComponents8 adds the components A, B, C, D, E, F, G, H to the existing EntityId.

It returns an error if:

  • the entity does not exist
  • the entity has one of the component
  • an internal error occurs

This solution is faster than an atomic solution.

func ConfigureComponent

func ConfigureComponent[T ComponentInterface](world *World, conf any) T

ConfigureComponent configures a Component of type T using the build function related to it.

The parameter conf contains all the data required for the configuration.

func GetComponent

func GetComponent[T ComponentInterface](world *World, entityId EntityId) *T

GetComponent returns a pointer to the component T owned by the entity.

If the entity does not have the component, it returns nil

func RegisterComponent

func RegisterComponent[T ComponentInterface](world *World, config ComponentConfigInterface)

RegisterComponent adds a component T to the registry of the given World.

Once the component is registered, it can be added to an entity.

func RemoveComponent

func RemoveComponent[T ComponentInterface](world *World, entityId EntityId) error

RemoveComponent removes the component to EntityId.

It returns an error if the EntityId does not have the component.

Types

type ArchetypesComponentsEntities

type ArchetypesComponentsEntities[T ComponentInterface] map[archetypeId][]T

type ComponentBuilder

type ComponentBuilder func(component any, configuration any)

ComponentBuilder is the function called to set the properties of a given component.

A type assertion is required on component and configuration parameters.

type ComponentConfig

type ComponentConfig[T ComponentInterface] struct {
	BuilderFn ComponentBuilder
	// contains filtered or unexported fields
}

Configuration for a component T.

BuilderFn defines the function called to set a new component.

type ComponentConfigInterface

type ComponentConfigInterface interface {
	// contains filtered or unexported methods
}

ComponentConfigInterface is the interface defining the method required to create a new Component.

type ComponentId

type ComponentId smallId

Component identifier in the register.

type ComponentIdConf added in v1.6.0

type ComponentIdConf struct {
	ComponentId
	// contains filtered or unexported fields
}

type ComponentInterface

type ComponentInterface interface {
	GetComponentId() ComponentId
}

ComponentInterface is the interface for all the Components.

It wraps the GetComponentId method, that returns a Component identifier.

type ComponentsRegister

type ComponentsRegister []ComponentConfigInterface

type ComponentsStorage

type ComponentsStorage[T ComponentInterface] struct {
	// contains filtered or unexported fields
}

type EntityId

type EntityId id

Entity identifier in the world.

func CreateEntityWithComponents2

func CreateEntityWithComponents2[A, B ComponentInterface](world *World, a A, b B) (EntityId, error)

CreateEntityWithComponents2 creates an entity in World; It sets the components A, B to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents3

func CreateEntityWithComponents3[A, B, C ComponentInterface](world *World, a A, b B, c C) (EntityId, error)

CreateEntityWithComponents3 creates an entity in World;

It sets the components A, B, C to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents4

func CreateEntityWithComponents4[A, B, C, D ComponentInterface](world *World, a A, b B, c C, d D) (EntityId, error)

CreateEntityWithComponents4 creates an entity in World;

It sets the components A, B, C, D to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents5

func CreateEntityWithComponents5[A, B, C, D, E ComponentInterface](world *World, a A, b B, c C, d D, e E) (EntityId, error)

CreateEntityWithComponents5 creates an entity in World;

It sets the components A, B, C, D, E to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents6

func CreateEntityWithComponents6[A, B, C, D, E, F ComponentInterface](world *World, a A, b B, c C, d D, e E, f F) (EntityId, error)

CreateEntityWithComponents6 creates an entity in World;

It sets the components A, B, C, D, E, F to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents7

func CreateEntityWithComponents7[A, B, C, D, E, F, G ComponentInterface](world *World, a A, b B, c C, d D, e E, f F, g G) (EntityId, error)

CreateEntityWithComponents7 creates an entity in World;

It sets the components A, B, C, D, E, F, G to the entity, for faster performances than the atomic version.

func CreateEntityWithComponents8

func CreateEntityWithComponents8[A, B, C, D, E, F, G, H ComponentInterface](world *World, a A, b B, c C, d D, e E, f F, g G, h H) (EntityId, error)

CreateEntityWithComponents8 creates an entity in World;

It sets the components A, B, C, D, E, F, G, H to the entity, for faster performances than the atomic version.

type OptionalComponent

type OptionalComponent ComponentId

Optional ComponentId for Queries.

type Query1

type Query1[A ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 1 component type.

func CreateQuery1

func CreateQuery1[A ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query1[A]

CreateQuery1 returns a new Query1, with component A.

func (*Query1[A]) Count

func (query *Query1[A]) Count() int

Count returns the total of entities fetched for Query1.

func (*Query1[A]) Foreach

func (query *Query1[A]) Foreach(filterFn func(QueryResult1[A]) bool) iter.Seq[QueryResult1[A]]

Foreach returns an iterator of QueryResult1 for all the entities with component A to which filterFn function returns true.

func (*Query1[A]) ForeachChannel deprecated

func (query *Query1[A]) ForeachChannel(chunkSize int, filterFn func(QueryResult1[A]) bool) <-chan iter.Seq[QueryResult1[A]]

ForeachChannel returns a channel of iterators of QueryResult1 for all the entities with component A to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query1[A]) GetComponentsIds

func (query *Query1[A]) GetComponentsIds() []ComponentId

func (*Query1[A]) GetEntitiesIds

func (query *Query1[A]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query1.

func (*Query1[A]) Task added in v1.8.0

func (query *Query1[A]) Task(workersCount int, filterFn func(QueryResult1[A]) bool, fn func(result QueryResult1[A]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult1. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query2

type Query2[A, B ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 2 components type.

func CreateQuery2

func CreateQuery2[A, B ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query2[A, B]

CreateQuery2 returns a new Query2, with components A, B.

func (*Query2[A, B]) Count

func (query *Query2[A, B]) Count() int

Count returns the total of entities fetched for Query2.

func (*Query2[A, B]) Foreach

func (query *Query2[A, B]) Foreach(filterFn func(QueryResult2[A, B]) bool) iter.Seq[QueryResult2[A, B]]

Foreach returns an iterator of QueryResult2 for all the entities with components A, B to which filterFn function returns true.

func (*Query2[A, B]) ForeachChannel deprecated

func (query *Query2[A, B]) ForeachChannel(chunkSize int, filterFn func(QueryResult2[A, B]) bool) <-chan iter.Seq[QueryResult2[A, B]]

ForeachChannel returns a channel of iterators of QueryResult2 for all the entities with components A, B to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query2[A, B]) GetComponentsIds

func (query *Query2[A, B]) GetComponentsIds() []ComponentId

func (*Query2[A, B]) GetEntitiesIds

func (query *Query2[A, B]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query2.

func (*Query2[A, B]) Task added in v1.8.0

func (query *Query2[A, B]) Task(workersCount int, filterFn func(QueryResult2[A, B]) bool, fn func(result QueryResult2[A, B]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult2. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query3

type Query3[A, B, C ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 3 components type.

func CreateQuery3

func CreateQuery3[A, B, C ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query3[A, B, C]

CreateQuery3 returns a new Query3, with components A, B, C.

func (*Query3[A, B, C]) Count

func (query *Query3[A, B, C]) Count() int

Count returns the total of entities fetched for Query3.

func (*Query3[A, B, C]) Foreach

func (query *Query3[A, B, C]) Foreach(filterFn func(QueryResult3[A, B, C]) bool) iter.Seq[QueryResult3[A, B, C]]

Foreach returns an iterator of QueryResult3 for all the entities with components A, B, C to which filterFn function returns true.

func (*Query3[A, B, C]) ForeachChannel deprecated

func (query *Query3[A, B, C]) ForeachChannel(chunkSize int, filterFn func(QueryResult3[A, B, C]) bool) <-chan iter.Seq[QueryResult3[A, B, C]]

ForeachChannel returns a channel of iterators of QueryResult3 for all the entities with components A, B, C to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query3[A, B, C]) GetComponentsIds

func (query *Query3[A, B, C]) GetComponentsIds() []ComponentId

func (*Query3[A, B, C]) GetEntitiesIds

func (query *Query3[A, B, C]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query3.

func (*Query3[A, B, C]) Task added in v1.8.0

func (query *Query3[A, B, C]) Task(workersCount int, filterFn func(QueryResult3[A, B, C]) bool, fn func(result QueryResult3[A, B, C]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult3. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query4

type Query4[A, B, C, D ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 4 components type.

func CreateQuery4

func CreateQuery4[A, B, C, D ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query4[A, B, C, D]

CreateQuery4 returns a new Query4, with components A, B, C, D.

func (*Query4[A, B, C, D]) Count

func (query *Query4[A, B, C, D]) Count() int

Count returns the total of entities fetched for Query4.

func (*Query4[A, B, C, D]) Foreach

func (query *Query4[A, B, C, D]) Foreach(filterFn func(QueryResult4[A, B, C, D]) bool) iter.Seq[QueryResult4[A, B, C, D]]

Foreach returns an iterator of QueryResult4 for all the entities with components A, B, C, D to which filterFn function returns true.

func (*Query4[A, B, C, D]) ForeachChannel deprecated

func (query *Query4[A, B, C, D]) ForeachChannel(chunkSize int, filterFn func(QueryResult4[A, B, C, D]) bool) <-chan iter.Seq[QueryResult4[A, B, C, D]]

ForeachChannel returns a channel of iterators of QueryResult4 for all the entities with components A, B, C, D to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query4[A, B, C, D]) GetComponentsIds

func (query *Query4[A, B, C, D]) GetComponentsIds() []ComponentId

func (*Query4[A, B, C, D]) GetEntitiesIds

func (query *Query4[A, B, C, D]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query4.

func (*Query4[A, B, C, D]) Task added in v1.8.0

func (query *Query4[A, B, C, D]) Task(workersCount int, filterFn func(QueryResult4[A, B, C, D]) bool, fn func(result QueryResult4[A, B, C, D]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult4. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query5

type Query5[A, B, C, D, E ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 5 components type.

func CreateQuery5

func CreateQuery5[A, B, C, D, E ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query5[A, B, C, D, E]

CreateQuery5 returns a new Query5, with components A, B, C, D, E.

func (*Query5[A, B, C, D, E]) Count

func (query *Query5[A, B, C, D, E]) Count() int

Count returns the total of entities fetched for Query5.

func (*Query5[A, B, C, D, E]) Foreach

func (query *Query5[A, B, C, D, E]) Foreach(filterFn func(QueryResult5[A, B, C, D, E]) bool) iter.Seq[QueryResult5[A, B, C, D, E]]

Foreach returns an iterator of QueryResult5 for all the entities with components A, B, C, D, E to which filterFn function returns true.

func (*Query5[A, B, C, D, E]) ForeachChannel deprecated

func (query *Query5[A, B, C, D, E]) ForeachChannel(chunkSize int, filterFn func(QueryResult5[A, B, C, D, E]) bool) <-chan iter.Seq[QueryResult5[A, B, C, D, E]]

ForeachChannel returns a channel of iterators of QueryResult5 for all the entities with components A, B, C, D, E to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query5[A, B, C, D, E]) GetComponentsIds

func (query *Query5[A, B, C, D, E]) GetComponentsIds() []ComponentId

func (*Query5[A, B, C, D, E]) GetEntitiesIds

func (query *Query5[A, B, C, D, E]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query5.

func (*Query5[A, B, C, D, E]) Task added in v1.8.0

func (query *Query5[A, B, C, D, E]) Task(workersCount int, filterFn func(QueryResult5[A, B, C, D, E]) bool, fn func(result QueryResult5[A, B, C, D, E]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult5. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query6

type Query6[A, B, C, D, E, F ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 6 components type.

func CreateQuery6

func CreateQuery6[A, B, C, D, E, F ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query6[A, B, C, D, E, F]

CreateQuery6 returns a new Query6, with components A, B, C, D, E, F.

func (*Query6[A, B, C, D, E, F]) Count

func (query *Query6[A, B, C, D, E, F]) Count() int

Count returns the total of entities fetched for Query6.

func (*Query6[A, B, C, D, E, F]) Foreach

func (query *Query6[A, B, C, D, E, F]) Foreach(filterFn func(QueryResult6[A, B, C, D, E, F]) bool) iter.Seq[QueryResult6[A, B, C, D, E, F]]

Foreach returns an iterator of QueryResult6 for all the entities with components A, B, C, D, E, F to which filterFn function returns true.

func (*Query6[A, B, C, D, E, F]) ForeachChannel deprecated

func (query *Query6[A, B, C, D, E, F]) ForeachChannel(chunkSize int, filterFn func(QueryResult6[A, B, C, D, E, F]) bool) <-chan iter.Seq[QueryResult6[A, B, C, D, E, F]]

ForeachChannel returns a channel of iterators of QueryResult6 for all the entities with components A, B, C, D, E, F to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query6[A, B, C, D, E, F]) GetComponentsIds

func (query *Query6[A, B, C, D, E, F]) GetComponentsIds() []ComponentId

func (*Query6[A, B, C, D, E, F]) GetEntitiesIds

func (query *Query6[A, B, C, D, E, F]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query6.

func (*Query6[A, B, C, D, E, F]) Task added in v1.8.0

func (query *Query6[A, B, C, D, E, F]) Task(workersCount int, filterFn func(QueryResult6[A, B, C, D, E, F]) bool, fn func(result QueryResult6[A, B, C, D, E, F]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult6. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query7

type Query7[A, B, C, D, E, F, G ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 7 components type.

func CreateQuery7

func CreateQuery7[A, B, C, D, E, F, G ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query7[A, B, C, D, E, F, G]

CreateQuery7 returns a new Query7, with components A, B, C, D, E, F, G.

func (*Query7[A, B, C, D, E, F, G]) Count

func (query *Query7[A, B, C, D, E, F, G]) Count() int

Count returns the total of entities fetched for Query7.

func (*Query7[A, B, C, D, E, F, G]) Foreach

func (query *Query7[A, B, C, D, E, F, G]) Foreach(filterFn func(QueryResult7[A, B, C, D, E, F, G]) bool) iter.Seq[QueryResult7[A, B, C, D, E, F, G]]

Foreach returns an iterator of QueryResult7 for all the entities with components A, B, C, D, E, F, G to which filterFn function returns true.

func (*Query7[A, B, C, D, E, F, G]) ForeachChannel deprecated

func (query *Query7[A, B, C, D, E, F, G]) ForeachChannel(chunkSize int, filterFn func(QueryResult7[A, B, C, D, E, F, G]) bool) <-chan iter.Seq[QueryResult7[A, B, C, D, E, F, G]]

ForeachChannel returns a channel of iterators of QueryResult7 for all the entities with components A, B, C, D, E, F, G to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query7[A, B, C, D, E, F, G]) GetComponentsIds

func (query *Query7[A, B, C, D, E, F, G]) GetComponentsIds() []ComponentId

func (*Query7[A, B, C, D, E, F, G]) GetEntitiesIds

func (query *Query7[A, B, C, D, E, F, G]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query7.

func (*Query7[A, B, C, D, E, F, G]) Task added in v1.8.0

func (query *Query7[A, B, C, D, E, F, G]) Task(workersCount int, filterFn func(QueryResult7[A, B, C, D, E, F, G]) bool, fn func(result QueryResult7[A, B, C, D, E, F, G]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult7. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type Query8

type Query8[A, B, C, D, E, F, G, H ComponentInterface] struct {
	World *World
	// contains filtered or unexported fields
}

Query for 8 components type.

func CreateQuery8

func CreateQuery8[A, B, C, D, E, F, G, H ComponentInterface](world *World, queryConfiguration QueryConfiguration) Query8[A, B, C, D, E, F, G, H]

CreateQuery8 returns a new Query8, with components A, B, C, D, E, F, G, H.

func (*Query8[A, B, C, D, E, F, G, H]) Count

func (query *Query8[A, B, C, D, E, F, G, H]) Count() int

Count returns the total of entities fetched for Query8.

func (*Query8[A, B, C, D, E, F, G, H]) Foreach

func (query *Query8[A, B, C, D, E, F, G, H]) Foreach(filterFn func(QueryResult8[A, B, C, D, E, F, G, H]) bool) iter.Seq[QueryResult8[A, B, C, D, E, F, G, H]]

Foreach returns an iterator of QueryResult8 for all the entities with components A, B, C, D, E, F, G, H to which filterFn function returns true.

func (*Query8[A, B, C, D, E, F, G, H]) ForeachChannel deprecated

func (query *Query8[A, B, C, D, E, F, G, H]) ForeachChannel(chunkSize int, filterFn func(QueryResult8[A, B, C, D, E, F, G, H]) bool) <-chan iter.Seq[QueryResult8[A, B, C, D, E, F, G, H]]

ForeachChannel returns a channel of iterators of QueryResult8 for all the entities with components A, B, C, D, E, F, G, H to which filterFn function returns true. The parameter chunkSize defines the size of each iterators.

Deprecated: ForeachChannel is deprecated and will be removed in a future version. Use Task(workersCount, filterFn, fn) instead, which offers better performance and a simpler API for parallel iteration.

func (*Query8[A, B, C, D, E, F, G, H]) GetComponentsIds

func (query *Query8[A, B, C, D, E, F, G, H]) GetComponentsIds() []ComponentId

func (*Query8[A, B, C, D, E, F, G, H]) GetEntitiesIds

func (query *Query8[A, B, C, D, E, F, G, H]) GetEntitiesIds() []EntityId

GetEntitiesIds returns a slice of all the EntityId fetched for Query8.

func (*Query8[A, B, C, D, E, F, G, H]) Task added in v1.8.0

func (query *Query8[A, B, C, D, E, F, G, H]) Task(workersCount int, filterFn func(QueryResult8[A, B, C, D, E, F, G, H]) bool, fn func(result QueryResult8[A, B, C, D, E, F, G, H]))

Task executes fn in parallel across workersCount goroutines for all entities matching the query. Each entity's components are passed to fn through QueryResult8. If filterFn is provided and returns false for an entity, that entity is skipped.

The workersCount parameter determines the number of parallel workers. Data is automatically partitioned across workers for optimal performance.

type QueryConfiguration added in v1.4.0

type QueryConfiguration struct {
	Tags               []TagId
	OptionalComponents []OptionalComponent
}

type QueryResult1

type QueryResult1[A ComponentInterface] struct {
	EntityId EntityId
	A        *A
}

Result returned for Query1.

type QueryResult2

type QueryResult2[A, B ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
}

Result returned for Query2.

type QueryResult3

type QueryResult3[A, B, C ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
}

Result returned for Query3.

type QueryResult4

type QueryResult4[A, B, C, D ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
	D        *D
}

Result returned for Query4.

type QueryResult5

type QueryResult5[A, B, C, D, E ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
	D        *D
	E        *E
}

Result returned for Query5.

type QueryResult6

type QueryResult6[A, B, C, D, E, F ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
	D        *D
	E        *E
	F        *F
}

Result returned for Query6.

type QueryResult7

type QueryResult7[A, B, C, D, E, F, G ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
	D        *D
	E        *E
	F        *F
	G        *G
}

Result returned for Query7.

type QueryResult8

type QueryResult8[A, B, C, D, E, F, G, H ComponentInterface] struct {
	EntityId EntityId
	A        *A
	B        *B
	C        *C
	D        *D
	E        *E
	F        *F
	G        *G
	H        *H
}

Result returned for Query8.

type TagId added in v1.4.0

type TagId = ComponentId

type World

type World struct {
	// contains filtered or unexported fields
}

World representation, container of all the data related to entities and their Components.

func CreateWorld

func CreateWorld(initialCapacity int) *World

CreateWorld returns a pointer to a new World.

It preallocates initialCapacity in memory.

func (*World) AddComponent

func (world *World) AddComponent(entityId EntityId, componentId ComponentId, conf any) error

AddComponent adds the component with ComponentId to the EntityId.

This non-generic version is adapted for when generics are not available, though might be slower. It returns an error if:

  • the entity already has the componentId
  • the componentId is not registered in the World
  • an internal error occurs

func (*World) AddComponents added in v1.6.0

func (world *World) AddComponents(entityId EntityId, componentsIdsConfs ...ComponentIdConf) error

AddComponents adds variadic components to the EntityId.

This non-generic version is adapted for when generics are not available, though might be slower. It returns an error if:

  • the entity already has the components Ids
  • the componentsIds are not registered in the World
  • an internal error occurs

func (*World) AddTag added in v1.4.0

func (world *World) AddTag(tagId TagId, entityId EntityId) error

AddTag adds a TagId to a given EntityId. This function returns an error if: - The id is lower than the valid range (< TAGS_INDICES) - The Tag is already owned

func (*World) Count added in v1.2.0

func (world *World) Count() int

Count returns the number of entities in World.

func (*World) CreateEntity

func (world *World) CreateEntity() EntityId

CreateEntity creates a new Entity in World; It is linked to no Component.

func (*World) GetComponent

func (world *World) GetComponent(entityId EntityId, componentId ComponentId) (any, error)

GetComponent returns the component with ComponentId for EntityId.

This non-generic version is adapted for when generics are not available, though might be slower and requires a type assertion. It returns an error if:

  • the ComponentId is not registered in the World
  • the entity does not have the component

func (*World) HasComponents

func (world *World) HasComponents(entityId EntityId, componentsIds ...ComponentId) bool

HasComponents returns whether the entity has the given variadic list of ComponentId.

It returns false if at least one ComponentId is not owned.

func (*World) HasTag added in v1.4.0

func (world *World) HasTag(tagId TagId, entityId EntityId) bool

HasTag returns a boolean, to check if an EntityId owns a Tag.

func (*World) PublishEntity

func (world *World) PublishEntity(entityId EntityId)

PublishEntity calls the callback setted in SetEntityAddedFn.

func (*World) RemoveComponent

func (world *World) RemoveComponent(entityId EntityId, componentId ComponentId) error

RemoveComponent removes the component with ComponentId from the EntityId.

This non-generic version is adapted for when generics are not available, though might be slower. It returns an error if:

  • the entity does not have the component
  • the ComponentId is not registered in the World

func (*World) RemoveEntity

func (world *World) RemoveEntity(entityId EntityId)

RemoveEntity removes all the data related to an Entity.

It calls the callback setted in SetEntityRemovedFn beforehand, so that the callback still has access to the data.

func (*World) RemoveTag added in v1.4.0

func (world *World) RemoveTag(tagId TagId, entityId EntityId) error

RemoveTags removes a Tag for a given EntityId. It returns an error if: - The entity does not exists. - The entity already owns the Tag.

func (*World) SetComponentAddedFn

func (world *World) SetComponentAddedFn(componentAddedFn func(entityId EntityId, componentId ComponentId))

SetComponentAddedFn sets a callback for when a component is added to an entity.

func (*World) SetComponentRemovedFn

func (world *World) SetComponentRemovedFn(componentRemovedFn func(entityId EntityId, componentId ComponentId))

SetComponentRemovedFn sets a callback for when a component is removed.

func (*World) SetEntityAddedFn

func (world *World) SetEntityAddedFn(entityAddedFn func(entityId EntityId))

SetEntityAddedFn sets a callback for when a new entity is added.

func (*World) SetEntityRemovedFn

func (world *World) SetEntityRemovedFn(entityRemovedFn func(entityId EntityId))

SetEntityRemovedFn sets a callback for when an entity is removed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL