ecb

package
v1.0.5-beta Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2023 License: LGPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package ecb allows for buffering of state changes to the ECS storage 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.

Manager.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 Manager 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, Manager 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 Manager.DiscardPending. A subsequent read will return identical data to the data stored in Redis.

Pending changes can be committed to redis with Manager.CommitPending. 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 CommitPending is called, reading this value from the DB will only ever return 0 or 100 (depending on the exact timing of the call).

Redis Storage 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 CommitPending 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")
)

Functions

This section is empty.

Types

type Manager

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

func NewManager

func NewManager(client *redis.Client) (*Manager, error)

NewManager 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 storage layer.

func (*Manager) AddComponentToEntity

func (m *Manager) AddComponentToEntity(cType component.ComponentMetadata, id entity.ID) error

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

func (*Manager) ArchetypeCount

func (m *Manager) ArchetypeCount() int

ArchetypeCount returns the number of archetypes that have been generated.

func (*Manager) Close

func (m *Manager) Close() error

Close closes the manager.

func (*Manager) CommitPending

func (m *Manager) CommitPending() error

CommitPending commits any pending state changes to the DB. If an error is returned, there will be no changes to the underlying DB.

func (*Manager) CreateEntity

func (m *Manager) CreateEntity(comps ...component.ComponentMetadata) (entity.ID, error)

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

func (*Manager) CreateManyEntities

func (m *Manager) CreateManyEntities(num int, comps ...component.ComponentMetadata) ([]entity.ID, error)

CreateManyEntities creates many entities with the given set of components.

func (*Manager) DiscardPending

func (m *Manager) DiscardPending()

DiscardPending discards any pending state changes.

func (*Manager) FinalizeTick

func (m *Manager) FinalizeTick(event *zerolog.Event) error

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

func (*Manager) GetArchIDForComponents

func (m *Manager) GetArchIDForComponents(components []component.ComponentMetadata) (archetype.ID, error)

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

func (*Manager) GetComponentForEntity

func (m *Manager) GetComponentForEntity(cType component.ComponentMetadata, id entity.ID) (any, error)

GetComponentForEntity returns the saved component data for the given entity.

func (*Manager) GetComponentForEntityInRawJSON

func (m *Manager) GetComponentForEntityInRawJSON(cType component.ComponentMetadata, id entity.ID) (
	json.RawMessage, error,
)

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

func (*Manager) GetComponentTypesForArchID

func (m *Manager) GetComponentTypesForArchID(archID archetype.ID) []component.ComponentMetadata

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

func (*Manager) GetComponentTypesForEntity

func (m *Manager) GetComponentTypesForEntity(id entity.ID) ([]component.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 (*Manager) GetEntitiesForArchID

func (m *Manager) GetEntitiesForArchID(archID archetype.ID) ([]entity.ID, error)

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

func (*Manager) GetTickNumbers

func (m *Manager) 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 (*Manager) InjectLogger

func (m *Manager) InjectLogger(logger *ecslog.Logger)

InjectLogger sets the logger for the manager.

func (*Manager) Recover

func (m *Manager) Recover(txs []message.Message) (*txpool.TxQueue, 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 (*Manager) RegisterComponents

func (m *Manager) RegisterComponents(comps []component.ComponentMetadata) error

func (*Manager) RemoveComponentFromEntity

func (m *Manager) RemoveComponentFromEntity(cType component.ComponentMetadata, id entity.ID) error

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

func (*Manager) RemoveEntity

func (m *Manager) RemoveEntity(idToRemove entity.ID) error

RemoveEntity removes the given entity from the ECS data model.

func (*Manager) SearchFrom

func (m *Manager) SearchFrom(filter filter.ComponentFilter, start int) *storage.ArchetypeIterator

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

func (*Manager) SetComponentForEntity

func (m *Manager) SetComponentForEntity(cType component.ComponentMetadata, id entity.ID, value any) error

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

func (*Manager) StartNextTick

func (m *Manager) StartNextTick(txs []message.Message, queue *txpool.TxQueue) 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 (*Manager) ToReadOnly

func (m *Manager) ToReadOnly() store.Reader

Jump to

Keyboard shortcuts

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