warehouse

package module
v0.0.1-early-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Mar 15, 2025 License: MIT Imports: 11 Imported by: 24

README

Warehouse

Warehouse is a high-performance Entity-Component-System (ECS) framework for Go, designed for games and simulations that require efficient entity management and querying. While its primary purpose is to be the underlying ECS for the Bappa Framework, it functions as a standalone ECS too.

Features

  • Component-based architecture: Build entities by composing reusable components
  • Archetype-based storage: Entities with the same component types are stored together for optimal cache utilization
  • Powerful query system: Find entities with specific component combinations using AND, OR, and NOT operations
  • Fast iteration: Optimized for performance with support for Go's iterator pattern

Installation

go get github.com/TheBitDrifter/warehouse

Quick Start

package main

import (
 "github.com/TheBitDrifter/table"
 "github.com/TheBitDrifter/warehouse"
)

// Define component types as structs
type Position struct {
 X, Y float64
}

type Velocity struct {
 X, Y float64
}

func main() {
 // Create a schema and storage
 schema := table.Factory.NewSchema()
 storage := warehouse.Factory.NewStorage(schema)

 // Create component accessors
 position := warehouse.FactoryNewComponent[Position]()
 velocity := warehouse.FactoryNewComponent[Velocity]()

 // Create entities with components
 entities, _ := storage.NewEntities(100, position, velocity)

 // Set values for the first entity
 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 entities with both position and velocity
 query := warehouse.Factory.NewQuery()
 queryNode := query.And(position, velocity)
 cursor := warehouse.Factory.NewCursor(queryNode, storage)

 // Process all matching entities
 for range cursor.Next() {
  pos := position.GetFromCursor(cursor)
  vel := velocity.GetFromCursor(cursor)
  
  // Update position based on velocity
  pos.X += vel.X
  pos.Y += vel.Y
 }
}

Core Concepts

Entities

Entities are game objects represented by a unique ID. They have no behavior of their own but gain functionality through attached components.

Components

Components are simple data structs that define attributes and state. They should follow single-responsibility principles and focus on related data.

Archetypes

Archetypes are collections of entities that share the same component types. This storage pattern optimizes memory layout for cache-friendly access.

Queries

Queries allow for finding entities with specific component combinations using logical operations (AND, OR, NOT).

Cursors

Cursors provide efficient iteration over query results for processing matched entities.

License

MIT License - see the LICENSE file for details.

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

Examples

Constants

This section is empty.

Variables

View Source
var Config config = config{}

Config holds global configuration for the table system

View Source
var Factory factory

Factory is the global factory instance for creating warehouse components.

Functions

This section is empty.

Types

type AccessibleComponent

type AccessibleComponent[T any] struct {
	Component
	table.Accessor[T] // concrete.
}

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

func FactoryNewCache[T any](cap int) Cache[T]

FactoryNewCache creates a new Cache with the specified capacity.

type CacheLocation

type CacheLocation struct {
	Key   string
	Index atomic.Uint32
}

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

func (c *Cursor) CurrentEntity() (Entity, error)

CurrentEntity returns the entity at the current cursor position

func (*Cursor) EntityAtOffset

func (c *Cursor) EntityAtOffset(offset int) (Entity, error)

EntityAtOffset returns an entity at the specified offset from current position

func (*Cursor) EntityIndex

func (c *Cursor) EntityIndex() int

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

func (c *Cursor) Next() iter.Seq[*Cursor]

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

func (c *Cursor) OldNext() bool

Deprecated OldNext advances to the next entity and returns whether one exists

func (*Cursor) RemainingInArchetype

func (c *Cursor) RemainingInArchetype() int

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

func (c *Cursor) TotalMatched() int

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

type EntityOperation interface {
	Apply(Storage) error
}

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 QueryNode

type QueryNode interface {
	Evaluate(archetype Archetype, storage Storage) bool
}

QueryNode represents a node in the query tree that can be evaluated

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

func (*SimpleCache[T]) Register

func (c *SimpleCache[T]) Register(key string, item T) (int, error)

Register adds a new item to the cache with the given key Returns the index of the newly added item or an error if the cache is full

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

Jump to

Keyboard shortcuts

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