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 ¶
- Variables
- type Manager
- func (m *Manager) AddComponentToEntity(cType component.ComponentMetadata, id entity.ID) error
- func (m *Manager) ArchetypeCount() int
- func (m *Manager) Close() error
- func (m *Manager) CommitPending() error
- func (m *Manager) CreateEntity(comps ...component.ComponentMetadata) (entity.ID, error)
- func (m *Manager) CreateManyEntities(num int, comps ...component.ComponentMetadata) ([]entity.ID, error)
- func (m *Manager) DiscardPending()
- func (m *Manager) FinalizeTick(event *zerolog.Event) error
- func (m *Manager) GetArchIDForComponents(components []component.ComponentMetadata) (archetype.ID, error)
- func (m *Manager) GetComponentForEntity(cType component.ComponentMetadata, id entity.ID) (any, error)
- func (m *Manager) GetComponentForEntityInRawJSON(cType component.ComponentMetadata, id entity.ID) (json.RawMessage, error)
- func (m *Manager) GetComponentTypesForArchID(archID archetype.ID) []component.ComponentMetadata
- func (m *Manager) GetComponentTypesForEntity(id entity.ID) ([]component.ComponentMetadata, error)
- func (m *Manager) GetEntitiesForArchID(archID archetype.ID) ([]entity.ID, error)
- func (m *Manager) GetTickNumbers() (start, end uint64, err error)
- func (m *Manager) InjectLogger(logger *ecslog.Logger)
- func (m *Manager) Recover(txs []message.Message) (*txpool.TxQueue, error)
- func (m *Manager) RegisterComponents(comps []component.ComponentMetadata) error
- func (m *Manager) RemoveComponentFromEntity(cType component.ComponentMetadata, id entity.ID) error
- func (m *Manager) RemoveEntity(idToRemove entity.ID) error
- func (m *Manager) SearchFrom(filter filter.ComponentFilter, start int) *storage.ArchetypeIterator
- func (m *Manager) SetComponentForEntity(cType component.ComponentMetadata, id entity.ID, value any) error
- func (m *Manager) StartNextTick(txs []message.Message, queue *txpool.TxQueue) error
- func (m *Manager) ToReadOnly() store.Reader
Constants ¶
This section is empty.
Variables ¶
var (
ErrArchetypeNotFound = errors.New("archetype for components not found")
)
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 ¶
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 ¶
AddComponentToEntity adds the given component to the given entity. An error is returned if the entity already has this component.
func (*Manager) ArchetypeCount ¶
ArchetypeCount returns the number of archetypes that have been generated.
func (*Manager) CommitPending ¶
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 ¶
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 ¶
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 ¶
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 ¶
GetEntitiesForArchID returns all the entities that currently belong to the given archetype ID.
func (*Manager) GetTickNumbers ¶
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 ¶
InjectLogger sets the logger for the manager.
func (*Manager) Recover ¶
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 ¶
RemoveComponentFromEntity removes the given component from the given entity. An error is returned if the entity does not have the component.
func (*Manager) RemoveEntity ¶
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 ¶
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.