gamestate

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2024 License: LGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package ecb allows for buffering of state changes to the ECS dbStorage layer, and either committing those changes in an atomic Redis transaction, or discarding the changes. In either case, the underlying Redis DB is never in an intermediate state.

Atomic options

There are two ways a batch of state changes can be grouped and applied/discarded.

EntityCommandBuffer.AtomicFn takes in a function that returns an error. The passed in function will be executed, and any state made during that function call will be stored as pending state changes. During this time, reads using the EntityCommandBuffer will report the pending values. Conversely, reading data directly from Redis the original value (before AtomicFn was called).

If the passed in function returns an error, all pending state changes will be discarded.

If the passed in function returns no error, all pending state changes will be committed to Redis in an atomic transaction.

Alternatively, EntityCommandBuffer can be used outside an AtomicFn context. State changes are stored as pending operations. Read operations will report the pending state. Note, no changes to Redis are applied while pending operations are accumulated.

Pending changes can be discarded with EntityCommandBuffer.DiscardPending. A subsequent read will return identical data to the data stored in Redis.

Pending changes can be committed to redis with EntityCommandBuffer.FinalizeTick. All pending changes will be packaged into a single redis [multi/exec pipeline](https://redis.io/docs/interact/transactions/) and applied atomically. Reads to redis during this time will never return any pending state. For example, if a series of 100 commands increments some value from 0 to 100, and then FinalizeTick is called, reading this value from the DB will only ever return 0 or 100 (depending on the exact timing of the call).

Redis PrimitiveStorage Model

The Redis keys that store data in redis are defined in keys.go. All keys are prefixed with "ECB".

key: "ECB:NEXT-ENTITY-ID" value: An integer that represents the next available entity ID that can be assigned to some entity. It can be assumed that entity IDs smaller than this value have already been assigned.

key: fmt.Sprintf("ECB:COMPONENT-VALUE:TYPE-ID-%d:ENTITY-ID-%d", componentTypeID, entityID) value: JSON serialized bytes that can be deserialized to the component with the matching componentTypeID. This component data has been assigned to the entity matching the entityID.

key: fmt.Sprintf("ECB:ARCHETYPE-ID:ENTITY-ID-%d", entityID) value: An integer that represents the archetype ID that the matching entityID has been assigned to.

key: fmt.Sprintf("ECB:ACTIVE-ENTITY-IDS:ARCHETYPE-ID-%d", archetypeID) value: JSON serialized bytes that can be deserialized to a slice of integers. The integers represent the entity IDs that currently belong to the matching archetypeID. Note, this is a reverse mapping of the previous key.

key: "ECB:ARCHETYPE-ID-TO-COMPONENT-TYPES" value: JSON serialized bytes that can be deserialized to a map of archetype.ID to []component.ID. This field represents what archetype IDs have already been assigned and what groups of components each archetype ID corresponds to. This field must be loaded into memory before any entity creation or component addition/removals take place.

key: "ECB:START-TICK" value: An integer that represents the last tick that was started.

key: "ECB:END-TICK" value: An integer that represents the last tick that was successfully completed.

key: "ECB:PENDING-TRANSACTIONS" value: JSON serialized bytes that can be deserialized to a list of transactions. These are the transactions that were processed in the last started tick. This data is only relevant when the START-TICK number does not match the END-TICK number.

In-memory storage model

The in-memory data model roughly matches the model that is stored in redis, but there are some differences:

Components are stored as generic interfaces and not as serialized JSON.

Potential Improvements

In redis, the ECB:ACTIVE-ENTITY-IDS and ECB:ARCHETYPE-ID:ENTITY-ID keys contains the same data, but are just reversed mapping of one another. The amount of data in redis, and the data written can likely be reduced if we abandon one of these keys and rebuild the other mapping in memory.

In memory, compValues are written to redis during a FinalizeTick cycle. Components that were not actually changed (e.g. only read operations were performed) are still written to the DB.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrArchetypeNotFound = errors.New("archetype for components not found")
)
View Source
var (
	ErrNoArchIDMappingFound = errors.New("no mapping of archID to components found")
)
View Source
var ErrNotFound = errors.New("key not found in map")

Functions

This section is empty.

Types

type EntityCommandBuffer

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

func NewEntityCommandBuffer

func NewEntityCommandBuffer(storage PrimitiveStorage[string]) (*EntityCommandBuffer, error)

NewEntityCommandBuffer creates a new command buffer manager that is able to queue up a series of states changes and atomically commit them to the underlying redis dbStorage layer.

func (*EntityCommandBuffer) AddComponentToEntity

func (m *EntityCommandBuffer) AddComponentToEntity(cType types.ComponentMetadata, id types.EntityID) error

AddComponentToEntity adds the given component to the given entity. An error is returned if the entity already has this component.

func (*EntityCommandBuffer) ArchetypeCount

func (m *EntityCommandBuffer) ArchetypeCount() int

ArchetypeCount returns the number of archetypes that have been generated.

func (*EntityCommandBuffer) Close

func (m *EntityCommandBuffer) Close() error

Close closes the manager.

func (*EntityCommandBuffer) CreateEntity

func (m *EntityCommandBuffer) CreateEntity(comps ...types.ComponentMetadata) (types.EntityID, error)

CreateEntity creates a single entity with the given set of components.

func (*EntityCommandBuffer) CreateManyEntities

func (m *EntityCommandBuffer) CreateManyEntities(num int, comps ...types.ComponentMetadata) ([]types.EntityID, error)

CreateManyEntities creates many entities with the given set of components.

func (*EntityCommandBuffer) DiscardPending

func (m *EntityCommandBuffer) DiscardPending() error

DiscardPending discards any pending state changes.

func (*EntityCommandBuffer) FinalizeTick

func (m *EntityCommandBuffer) FinalizeTick(ctx context.Context) error

FinalizeTick combines all pending state changes into a single multi/exec redis transactions and commits them to the DB.

func (*EntityCommandBuffer) GetArchIDForComponents

func (m *EntityCommandBuffer) GetArchIDForComponents(components []types.ComponentMetadata) (types.ArchetypeID, error)

GetArchIDForComponents returns the archetype EntityID that has been assigned to this set of components. If this set of components does not have an archetype EntityID assigned to it, an error is returned.

func (*EntityCommandBuffer) GetComponentForEntity

func (m *EntityCommandBuffer) GetComponentForEntity(cType types.ComponentMetadata, id types.EntityID) (any, error)

GetComponentForEntity returns the saved component data for the given entity.

func (*EntityCommandBuffer) GetComponentForEntityInRawJSON

func (m *EntityCommandBuffer) GetComponentForEntityInRawJSON(cType types.ComponentMetadata, id types.EntityID) (
	json.RawMessage, error,
)

GetComponentForEntityInRawJSON returns the saved component data as JSON encoded bytes for the given entity.

func (*EntityCommandBuffer) GetComponentTypesForArchID

func (m *EntityCommandBuffer) GetComponentTypesForArchID(archID types.ArchetypeID) ([]types.ComponentMetadata, error)

GetComponentTypesForArchID returns the set of components that are associated with the given archetype id.

func (*EntityCommandBuffer) GetComponentTypesForEntity

func (m *EntityCommandBuffer) GetComponentTypesForEntity(id types.EntityID) ([]types.ComponentMetadata, error)

GetComponentTypesForEntity returns all the component types that are currently on the given entity. Only types are returned. To get the actual component data, use GetComponentForEntity.

func (*EntityCommandBuffer) GetEntitiesForArchID

func (m *EntityCommandBuffer) GetEntitiesForArchID(archID types.ArchetypeID) ([]types.EntityID, error)

GetEntitiesForArchID returns all the entities that currently belong to the given archetype EntityID.

func (*EntityCommandBuffer) GetTickNumbers

func (m *EntityCommandBuffer) GetTickNumbers() (start, end uint64, err error)

GetTickNumbers returns the last tick that was started and the last tick that was ended. If start == end, it means the last tick that was attempted completed successfully. If start != end, it means a tick was started but did not complete successfully; Recover must be used to recover the pending transactions so the previously started tick can be completed.

func (*EntityCommandBuffer) Recover

func (m *EntityCommandBuffer) Recover(txs []types.Message) (*txpool.TxPool, error)

Recover fetches the pending transactions for an incomplete tick. This should only be called if GetTickNumbers indicates that the previous tick was started, but never completed.

func (*EntityCommandBuffer) RegisterComponents

func (m *EntityCommandBuffer) RegisterComponents(comps []types.ComponentMetadata) error

func (*EntityCommandBuffer) RemoveComponentFromEntity

func (m *EntityCommandBuffer) RemoveComponentFromEntity(cType types.ComponentMetadata, id types.EntityID) error

RemoveComponentFromEntity removes the given component from the given entity. An error is returned if the entity does not have the component.

func (*EntityCommandBuffer) RemoveEntity

func (m *EntityCommandBuffer) RemoveEntity(idToRemove types.EntityID) error

RemoveEntity removes the given entity from the ECS data model.

func (*EntityCommandBuffer) SearchFrom

SearchFrom returns an ArchetypeIterator based on a component filter. The iterator will iterate over all archetypes that match the given filter.

func (*EntityCommandBuffer) SetComponentForEntity

func (m *EntityCommandBuffer) SetComponentForEntity(
	cType types.ComponentMetadata,
	id types.EntityID, value any,
) error

SetComponentForEntity sets the given entity's component data to the given value.

func (*EntityCommandBuffer) StartNextTick

func (m *EntityCommandBuffer) StartNextTick(txs []types.Message, pool *txpool.TxPool) error

StartNextTick saves the given transactions to the DB and sets the tick trackers to indicate we are in the middle of a tick. While transactions are saved to the DB, no state changes take place at this time.

func (*EntityCommandBuffer) ToReadOnly

func (m *EntityCommandBuffer) ToReadOnly() Reader

type Manager

type Manager interface {
	TickStorage
	Reader
	Writer
	ToReadOnly() Reader
}

Manager represents all the methods required to track Component, Entity, and Archetype information which powers the ECS dbStorage layer.

type MapStorage

type MapStorage[K comparable, V any] struct {
	// contains filtered or unexported fields
}

func NewMapStorage

func NewMapStorage[K comparable, V any]() *MapStorage[K, V]

func (*MapStorage[K, V]) Clear

func (m *MapStorage[K, V]) Clear() error

func (*MapStorage[K, V]) Delete

func (m *MapStorage[K, V]) Delete(key K) error

func (*MapStorage[K, V]) Get

func (m *MapStorage[K, V]) Get(key K) (V, error)

func (*MapStorage[K, V]) Keys

func (m *MapStorage[K, V]) Keys() ([]K, error)

func (*MapStorage[K, V]) Len

func (m *MapStorage[K, V]) Len() int

func (*MapStorage[K, V]) Set

func (m *MapStorage[K, V]) Set(key K, value V) error

type PrimitiveStorage

type PrimitiveStorage[K comparable] interface {
	GetFloat64(ctx context.Context, key K) (float64, error)
	GetFloat32(ctx context.Context, key K) (float32, error)
	GetUInt64(ctx context.Context, key K) (uint64, error)
	GetInt64(ctx context.Context, key K) (int64, error)
	GetInt(ctx context.Context, key K) (int, error)
	GetBool(ctx context.Context, key K) (bool, error)
	GetBytes(ctx context.Context, key K) ([]byte, error)
	Get(ctx context.Context, key K) (any, error)
	Set(ctx context.Context, key K, value any) error
	Incr(ctx context.Context, key K) error
	Decr(ctx context.Context, key K) error
	Delete(ctx context.Context, key K) error
	StartTransaction(ctx context.Context) (Transaction[K], error)
	EndTransaction(ctx context.Context) error
	Close(ctx context.Context) error
	Clear(ctx context.Context) error
	Keys(ctx context.Context) ([]K, error)
}

PrimitiveStorage is the interface for all available stores related to the game loop there is another store like interface for other logistical values located in `ecs.storage`

type Reader

type Reader interface {
	// One Component One Entity
	GetComponentForEntity(cType types.ComponentMetadata, id types.EntityID) (any, error)
	GetComponentForEntityInRawJSON(cType types.ComponentMetadata, id types.EntityID) (json.RawMessage, error)

	// Many Components One Entity
	GetComponentTypesForEntity(id types.EntityID) ([]types.ComponentMetadata, error)

	// One Archetype Many Components
	GetComponentTypesForArchID(archID types.ArchetypeID) ([]types.ComponentMetadata, error)
	GetArchIDForComponents(components []types.ComponentMetadata) (types.ArchetypeID, error)

	// One Archetype Many Entities
	GetEntitiesForArchID(archID types.ArchetypeID) ([]types.EntityID, error)

	// Misc
	SearchFrom(filter filter.ComponentFilter, start int) *iterators.ArchetypeIterator
	ArchetypeCount() int
}

type RedisStorage

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

func NewRedisPrimitiveStorage

func NewRedisPrimitiveStorage(client redis.Cmdable) RedisStorage

func (*RedisStorage) Clear

func (r *RedisStorage) Clear(ctx context.Context) error

func (*RedisStorage) Close

func (r *RedisStorage) Close(ctx context.Context) error

func (*RedisStorage) Decr

func (r *RedisStorage) Decr(ctx context.Context, key string) error

func (*RedisStorage) Delete

func (r *RedisStorage) Delete(ctx context.Context, key string) error

func (*RedisStorage) EndTransaction

func (r *RedisStorage) EndTransaction(ctx context.Context) error

func (*RedisStorage) Get

func (r *RedisStorage) Get(ctx context.Context, key string) (any, error)

Underlying type is a string. Unfortunately this is the way redis works and this is the most generic return value.

func (*RedisStorage) GetBool

func (r *RedisStorage) GetBool(ctx context.Context, key string) (bool, error)

func (*RedisStorage) GetBytes

func (r *RedisStorage) GetBytes(ctx context.Context, key string) ([]byte, error)

func (*RedisStorage) GetFloat32

func (r *RedisStorage) GetFloat32(ctx context.Context, key string) (float32, error)

func (*RedisStorage) GetFloat64

func (r *RedisStorage) GetFloat64(ctx context.Context, key string) (float64, error)

func (*RedisStorage) GetInt

func (r *RedisStorage) GetInt(ctx context.Context, key string) (int, error)

func (*RedisStorage) GetInt64

func (r *RedisStorage) GetInt64(ctx context.Context, key string) (int64, error)

func (*RedisStorage) GetUInt64

func (r *RedisStorage) GetUInt64(ctx context.Context, key string) (uint64, error)

func (*RedisStorage) Incr

func (r *RedisStorage) Incr(ctx context.Context, key string) error

func (*RedisStorage) Keys

func (r *RedisStorage) Keys(ctx context.Context) ([]string, error)

func (*RedisStorage) Set

func (r *RedisStorage) Set(ctx context.Context, key string, value any) error

func (*RedisStorage) StartTransaction

func (r *RedisStorage) StartTransaction(_ context.Context) (Transaction[string], error)

type TickStorage

type TickStorage interface {
	GetTickNumbers() (start, end uint64, err error)
	StartNextTick(txs []types.Message, pool *txpool.TxPool) error
	FinalizeTick(ctx context.Context) error
	Recover(txs []types.Message) (*txpool.TxPool, error)
}

type Transaction

type Transaction[K comparable] interface {
	PrimitiveStorage[K]
}

type VolatileStorage

type VolatileStorage[K comparable, V any] interface {
	Get(key K) (V, error)
	Set(key K, value V) error
	Delete(key K) error
	Keys() ([]K, error)
	Clear() error
	Len() int
}

this interface is meant for in memory storage

type Writer

type Writer interface {
	// One Entity
	RemoveEntity(id types.EntityID) error

	// Many Components
	CreateEntity(comps ...types.ComponentMetadata) (types.EntityID, error)
	CreateManyEntities(num int, comps ...types.ComponentMetadata) ([]types.EntityID, error)

	// One Component One Entity
	SetComponentForEntity(cType types.ComponentMetadata, id types.EntityID, value any) error
	AddComponentToEntity(cType types.ComponentMetadata, id types.EntityID) error
	RemoveComponentFromEntity(cType types.ComponentMetadata, id types.EntityID) error

	// Misc
	Close() error
	RegisterComponents([]types.ComponentMetadata) error
}

Jump to

Keyboard shortcuts

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