Documentation
¶
Overview ¶
Package warehouse provides an Entity-Component-System (ECS) framework for games and simulations.
Warehouse offers a performant approach to managing game entities through component-based design. It's built on an archetype-based storage system that keeps entities with the same component types together for optimal cache utilization.
Core Concepts:
- Entity: A unique identifier that represents a game object.
- Component: A data container that defines entity attributes.
- Archetype: A collection of entities sharing the same component types.
- Query: A way to find entities with specific component combinations.
Basic Usage:
// Create storage with schema
schema := table.Factory.NewSchema()
storage := warehouse.Factory.NewStorage(schema)
// Define components
position := warehouse.FactoryNewComponent[Position]()
velocity := warehouse.FactoryNewComponent[Velocity]()
// Create entities
entities, _ := storage.NewEntities(100, position, velocity)
// Query entities and process them
query := warehouse.Factory.NewQuery()
queryNode := query.And(position, velocity)
cursor := warehouse.Factory.NewCursor(queryNode, storage)
for range cursor.Next() {
pos := position.GetFromCursor(cursor)
vel := velocity.GetFromCursor(cursor)
pos.X += vel.X
pos.Y += vel.Y
}
Warehouse is the underlying ECS for the Bappa Framework but also works as a standalone library.
Package warehouse provides query mechanisms for component-based entity systems
Example (Basic) ¶
Example shows basic warehouse usage with entity creation and queries
package main
import (
"fmt"
"github.com/TheBitDrifter/table"
"github.com/TheBitDrifter/warehouse"
)
// Position is a simple component for 2D coordinates
type Position struct {
X float64
Y float64
}
// Velocity is a simple component for 2D movement
type Velocity struct {
X float64
Y float64
}
// Name is a simple component for entity identification
type Name struct {
Value string
}
func main() {
// Create storage
schema := table.Factory.NewSchema()
storage := warehouse.Factory.NewStorage(schema)
// Define components
position := warehouse.FactoryNewComponent[Position]()
velocity := warehouse.FactoryNewComponent[Velocity]()
name := warehouse.FactoryNewComponent[Name]()
// Create entities
storage.NewEntities(5, position)
storage.NewEntities(3, position, velocity)
// Create one named entity
entities, _ := storage.NewEntities(1, position, velocity, name)
nameComp := name.GetFromEntity(entities[0])
nameComp.Value = "Player"
// Set position and velocity
pos := position.GetFromEntity(entities[0])
vel := velocity.GetFromEntity(entities[0])
pos.X, pos.Y = 10.0, 20.0
vel.X, vel.Y = 1.0, 2.0
// Query for all entities with position and velocity
query := warehouse.Factory.NewQuery()
queryNode := query.And(position, velocity)
cursor := warehouse.Factory.NewCursor(queryNode, storage)
// Count matching entities
matchCount := 0
for range cursor.Next() {
matchCount++
}
fmt.Printf("Found %d entities with position and velocity\n", matchCount)
// Query for just the named entity
query = warehouse.Factory.NewQuery()
queryNode = query.And(name)
cursor = warehouse.Factory.NewCursor(queryNode, storage)
// Process the named entity
for range cursor.Next() {
pos := position.GetFromCursor(cursor)
vel := velocity.GetFromCursor(cursor)
nme := name.GetFromCursor(cursor)
// Update position based on velocity
pos.X += vel.X
pos.Y += vel.Y
fmt.Printf("Updated %s to position (%.1f, %.1f)\n", nme.Value, pos.X, pos.Y)
}
}
Output: Found 4 entities with position and velocity Updated Player to position (11.0, 22.0)
Example (Queries) ¶
Example_queries shows how to use different query operations
package main
import (
"fmt"
"github.com/TheBitDrifter/table"
"github.com/TheBitDrifter/warehouse"
)
// Position is a simple component for 2D coordinates
type Position struct {
X float64
Y float64
}
// Velocity is a simple component for 2D movement
type Velocity struct {
X float64
Y float64
}
// Name is a simple component for entity identification
type Name struct {
Value string
}
func main() {
// Create storage
schema := table.Factory.NewSchema()
storage := warehouse.Factory.NewStorage(schema)
// Define components
position := warehouse.FactoryNewComponent[Position]()
velocity := warehouse.FactoryNewComponent[Velocity]()
name := warehouse.FactoryNewComponent[Name]()
// Create different entity types
storage.NewEntities(3, position)
storage.NewEntities(3, position, velocity)
storage.NewEntities(3, position, name)
storage.NewEntities(3, position, velocity, name)
// AND query: entities with position AND velocity
query := warehouse.Factory.NewQuery()
andQuery := query.And(position, velocity)
cursor := warehouse.Factory.NewCursor(andQuery, storage)
fmt.Printf("AND query matched %d entities\n", cursor.TotalMatched())
// OR query: entities with velocity OR name
orQuery := query.Or(velocity, name)
cursor = warehouse.Factory.NewCursor(orQuery, storage)
fmt.Printf("OR query matched %d entities\n", cursor.TotalMatched())
// NOT query: entities with position but NOT velocity
notQuery := query.And(position)
notQuery = query.Not(velocity)
cursor = warehouse.Factory.NewCursor(notQuery, storage)
fmt.Printf("NOT query matched %d entities\n", cursor.TotalMatched())
}
Output: AND query matched 6 entities OR query matched 9 entities NOT query matched 6 entities
Index ¶
- Variables
- type AccessibleComponent
- type AddComponentOperation
- type Archetype
- type ArchetypeImpl
- type Cache
- type CacheLocation
- type Component
- type Cursor
- func (c *Cursor) CurrentEntity() (Entity, error)
- func (c *Cursor) EntityAtOffset(offset int) (Entity, error)
- func (c *Cursor) EntityIndex() int
- func (c *Cursor) Initialize()
- func (c *Cursor) Next() iter.Seq[*Cursor]
- func (c *Cursor) OldNext() bool
- func (c *Cursor) RemainingInArchetype() int
- func (c *Cursor) Reset()
- func (c *Cursor) TotalMatched() int
- type DestroyEntityOperation
- type Entity
- type EntityDestroyCallback
- type EntityOperation
- type EntityOperationsQueue
- type NewEntityOperation
- type Query
- type QueryNode
- type QueryOperation
- type RemoveComponentOperation
- type SimpleCache
- type Storage
- type TransferEntityOperation
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Config config = config{}
Config holds global configuration for the table system
var Factory factory
Factory is the global factory instance for creating warehouse components.
Functions ¶
This section is empty.
Types ¶
type AccessibleComponent ¶
AccessibleComponent extends a base Component with table-based accessibility It provides methods to retrieve components using different access patterns
func FactoryNewComponent ¶
func FactoryNewComponent[T any]() AccessibleComponent[T]
FactoryNewComponent creates a new AccessibleComponent for type T.
func (AccessibleComponent[T]) CheckCursor ¶
func (c AccessibleComponent[T]) CheckCursor(cursor *Cursor) bool
CheckCursor determines if the component exists in the archetype at the cursor position
func (AccessibleComponent[T]) GetFromCursor ¶
func (c AccessibleComponent[T]) GetFromCursor(cursor *Cursor) *T
GetFromCursor retrieves a component value for the entity at the cursor position
func (AccessibleComponent[T]) GetFromCursorSafe ¶
func (c AccessibleComponent[T]) GetFromCursorSafe(cursor *Cursor) (bool, *T)
GetFromCursorSafe safely retrieves a component value, checking if the component exists Returns a boolean indicating success and the component pointer if found
func (AccessibleComponent[T]) GetFromEntity ¶
func (c AccessibleComponent[T]) GetFromEntity(entity Entity) *T
GetFromEntity retrieves a component value for the specified entity
type AddComponentOperation ¶
type AddComponentOperation struct {
// contains filtered or unexported fields
}
AddComponentOperation adds a component to an entity
func (AddComponentOperation) Apply ¶
func (op AddComponentOperation) Apply(sto Storage) error
Apply adds the component to the entity if conditions are met
type Archetype ¶
type Archetype interface {
// ID returns the unique identifier of the ArchetypeImpl
ID() uint32
// Table returns the underlying data table for the ArchetypeImpl
Table() table.Table
// Generate creates entities with the specified components
Generate(count int, fromComponents ...any) error
}
Archetype represents a collection of entities with the same component types
type ArchetypeImpl ¶
type ArchetypeImpl struct {
// contains filtered or unexported fields
}
ArchetypeImpl is the concrete implementation of the Archetype interface
func (ArchetypeImpl) Generate ¶
func (a ArchetypeImpl) Generate(count int, fromComponents ...any) error
Generate creates the specified number of entities with optional component values
func (ArchetypeImpl) ID ¶
func (a ArchetypeImpl) ID() uint32
ID returns the unique identifier of the ArchetypeImpl
func (ArchetypeImpl) Table ¶
func (a ArchetypeImpl) Table() table.Table
Table returns the underlying data table for the ArchetypeImpl
type Cache ¶
type Cache[T any] interface { // GetIndex retrieves the index of an item by its key GetIndex(string) (int, bool) // GetItem retrieves an item by its index GetItem(int) T // GetItem32 retrieves an item by its uint32 index GetItem32(uint32) T // Register adds a new item to the cache with the given key Register(string, T) (int, error) }
Cache defines a generic thread-safe cache interface
func FactoryNewCache ¶
FactoryNewCache creates a new Cache with the specified capacity.
type CacheLocation ¶
CacheLocation represents the position of an item in a cache
type Component ¶
type Component interface {
table.ElementType
}
Component represents a data container that can be attached to entities. Components define the attributes and properties of entities without containing behavior.
Each component type should be a struct with the data fields needed for that attribute.
type Cursor ¶
type Cursor struct {
// contains filtered or unexported fields
}
Cursor provides iteration over filtered entities in storage
func (*Cursor) CurrentEntity ¶
CurrentEntity returns the entity at the current cursor position
func (*Cursor) EntityAtOffset ¶
EntityAtOffset returns an entity at the specified offset from current position
func (*Cursor) EntityIndex ¶
EntityIndex returns the current entity index within the current archetype
func (*Cursor) Initialize ¶
func (c *Cursor) Initialize()
Initialize sets up the cursor by finding matching archetypes
func (*Cursor) Next ¶
Next advances the cursor and returns it
Whats especially useful about using an iterator pattern here (instead of the deprecated loop version) is the automatic cleanup via the magic yield func — It's pretty lit! It also didn't exist when I first started looking into this project, so that's pretty cool
func (*Cursor) OldNext ¶
Deprecated OldNext advances to the next entity and returns whether one exists
func (*Cursor) RemainingInArchetype ¶
RemainingInArchetype returns the number of entities left in the current archetype
func (*Cursor) Reset ¶
func (c *Cursor) Reset()
Reset clears cursor state and releases the storage lock
func (*Cursor) TotalMatched ¶
TotalMatched returns the total number of entities matching the query
type DestroyEntityOperation ¶
type DestroyEntityOperation struct {
// contains filtered or unexported fields
}
DestroyEntityOperation removes an entity from storage
func (DestroyEntityOperation) Apply ¶
func (op DestroyEntityOperation) Apply(sto Storage) error
Apply destroys the entity if it's valid and has the expected recycled value
type Entity ¶
type Entity interface {
table.Entry
// AddComponent attaches a component to this entity
AddComponent(Component) error
// AddComponentWithValue attaches a component with an initial value
AddComponentWithValue(Component, any) error
// RemoveComponent detaches a component from this entity
RemoveComponent(Component) error
// EnqueueAddComponent schedules a component addition when storage is locked
EnqueueAddComponent(Component) error
// EnqueueAddComponentWithValue schedules a component addition with value when storage is locked
EnqueueAddComponentWithValue(Component, any) error
// EnqueueRemoveComponent schedules a component removal when storage is locked
EnqueueRemoveComponent(Component) error
// Components returns all components attached to this entity
Components() []Component
// ComponentsAsString returns a string representation of component names
ComponentsAsString() string
// Valid returns whether this entity has a valid ID
Valid() bool
// Storage returns the storage this entity belongs to
Storage() Storage
// SetStorage changes the storage this entity belongs to
SetStorage(Storage)
}
Entity represents a game object that's composed of components. Entities are essentially IDs that tie related components together.
An entity's behavior is determined by which components are attached to it. Adding or removing components changes the entity's archetype (its component signature).
type EntityDestroyCallback ¶
type EntityDestroyCallback func(Entity)
EntityDestroyCallback is called when an entity is destroyed
type EntityOperation ¶
EntityOperation represents an operation that can be applied to a storage
type EntityOperationsQueue ¶
type EntityOperationsQueue interface {
Enqueue(EntityOperation)
ProcessAll(Storage) error
}
EntityOperationsQueue provides an interface for queuing and processing operations
type NewEntityOperation ¶
type NewEntityOperation struct {
// contains filtered or unexported fields
}
NewEntityOperation creates multiple entities with the same components
func (NewEntityOperation) Apply ¶
func (op NewEntityOperation) Apply(sto Storage) error
Apply creates entities with the specified components
type Query ¶
type Query interface {
QueryNode
And(items ...interface{}) QueryNode
Or(items ...interface{}) QueryNode
Not(items ...interface{}) QueryNode
}
Query represents a composable query interface for filtering entities
type QueryOperation ¶
type QueryOperation int
QueryOperation defines the logical operations for query nodes
const ( OpAnd QueryOperation = iota // Logical AND operation OpOr // Logical OR operation OpNot // Logical NOT operation )
type RemoveComponentOperation ¶
type RemoveComponentOperation struct {
// contains filtered or unexported fields
}
RemoveComponentOperation removes a component from an entity
func (RemoveComponentOperation) Apply ¶
func (op RemoveComponentOperation) Apply(sto Storage) error
Apply removes the component from the entity if conditions are met
type SimpleCache ¶
type SimpleCache[T any] struct { // contains filtered or unexported fields }
SimpleCache implements the Cache interface with a slice-backed storage
func (*SimpleCache[T]) Clear ¶
func (c *SimpleCache[T]) Clear()
Clear removes all items from the cache
func (*SimpleCache[T]) GetIndex ¶
func (c *SimpleCache[T]) GetIndex(key string) (int, bool)
GetIndex retrieves the index of an item by its key
func (*SimpleCache[T]) GetItem ¶
func (c *SimpleCache[T]) GetItem(index int) T
GetItem retrieves an item by its index
func (*SimpleCache[T]) GetItem32 ¶
func (c *SimpleCache[T]) GetItem32(index uint32) T
GetItem32 retrieves an item by its uint32 index
type Storage ¶
type Storage interface {
Entity(id int) (Entity, error)
NewEntities(int, ...Component) ([]Entity, error)
NewOrExistingArchetype(components ...Component) (Archetype, error)
EnqueueNewEntities(int, ...Component) error
DestroyEntities(...Entity) error
EnqueueDestroyEntities(...Entity) error
RowIndexFor(Component) uint32
Locked() bool
AddLock(bit uint32)
RemoveLock(bit uint32)
Register(...Component)
TransferEntities(target Storage, entities ...Entity) error
Enqueue(EntityOperation)
Archetypes() []ArchetypeImpl
TotalEntities() int
// contains filtered or unexported methods
}
Storage defines the interface for entity storage and manipulation
type TransferEntityOperation ¶
type TransferEntityOperation struct {
// contains filtered or unexported fields
}
TransferEntityOperation moves an entity from one storage to another
func (TransferEntityOperation) Apply ¶
func (op TransferEntityOperation) Apply(sto Storage) error
Apply transfers the entity if it's valid and has the expected recycled value