Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve thread safety and performance #131

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 37 additions & 52 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,40 @@ type Entry struct {
}

// Get returns the component from the entry
func Get[T any](e *Entry, ctype component.IComponentType) *T {
return (*T)(e.Component(ctype))
func Get[T any](e *Entry, c component.IComponentType) *T {
return (*T)(e.Component(c))
}

// GetComponents uses reflection to convert the unsafe.Pointers of an entry into its component data instances.
// Note that this is likely to be slow and should not be used in hot paths or if not necessary.
func GetComponents(e *Entry) []any {
archetypeIdx := e.loc.Archetype
s := e.World.StorageAccessor().Archetypes[archetypeIdx]
cs := s.ComponentTypes()
var instances []any
for _, ctyp := range cs {
instancePtr := e.Component(ctyp)
componentType := ctyp.Typ()
val := reflect.NewAt(componentType, instancePtr)
valInstance := reflect.Indirect(val).Interface()
instances = append(instances, valInstance)
for _, c := range e.World.StorageAccessor().Archetypes[e.loc.Archetype].ComponentTypes() {
instances = append(instances, reflect.Indirect(
reflect.NewAt(c.Typ(), e.Component(c))).Interface(),
)
}
return instances
}

// Add adds the component to the entry.
func Add[T any](e *Entry, ctype component.IComponentType, component *T) {
e.AddComponent(ctype, unsafe.Pointer(component))
func Add[T any](e *Entry, c component.IComponentType, component *T) {
e.AddComponent(c, unsafe.Pointer(component))
}

// Set sets the comopnent of the entry.
func Set[T any](e *Entry, ctype component.IComponentType, component *T) {
e.SetComponent(ctype, unsafe.Pointer(component))
func Set[T any](e *Entry, c component.IComponentType, component *T) {
e.SetComponent(c, unsafe.Pointer(component))
}

// SetValue sets the value of the component.
func SetValue[T any](e *Entry, ctype component.IComponentType, value T) {
*Get[T](e, ctype) = value
func SetValue[T any](e *Entry, c component.IComponentType, value T) {
*Get[T](e, c) = value
}

// GetValue gets the value of the component.
func GetValue[T any](e *Entry, ctype component.IComponentType) T {
return *Get[T](e, ctype)
func GetValue[T any](e *Entry, c component.IComponentType) T {
return *Get[T](e, c)
}

// Remove removes the component from the entry.
Expand All @@ -85,60 +80,50 @@ func (e *Entry) Entity() Entity {
}

// Component returns the component.
func (e *Entry) Component(ctype component.IComponentType) unsafe.Pointer {
c := e.loc.Component
a := e.loc.Archetype
return e.World.components.Storage(ctype).Component(a, c)
func (e *Entry) Component(c component.IComponentType) unsafe.Pointer {
return e.World.components.Storage(c).Component(e.loc.Archetype, e.loc.Component)
}

// SetComponent sets the component.
func (e *Entry) SetComponent(ctype component.IComponentType, component unsafe.Pointer) {
c := e.loc.Component
a := e.loc.Archetype
e.World.components.Storage(ctype).SetComponent(a, c, component)
func (e *Entry) SetComponent(c component.IComponentType, component unsafe.Pointer) {
e.World.components.Storage(c).SetComponent(e.loc.Archetype, e.loc.Component, component)
}

// AddComponent adds the component to the entity.
func (e *Entry) AddComponent(ctype component.IComponentType, components ...unsafe.Pointer) {
func (e *Entry) AddComponent(c component.IComponentType, components ...unsafe.Pointer) {
if len(components) > 1 {
panic("AddComponent: component argument must be a single value")
}
if !e.HasComponent(ctype) {
c := e.loc.Component
a := e.loc.Archetype

base_layout := e.World.archetypes[a].Layout().Components()
target_arc := e.World.getArchetypeForComponents(append(base_layout, ctype))
e.World.TransferArchetype(a, target_arc, c)

if !e.HasComponent(c) {
archetypeIndex := e.loc.Archetype
targetArchetype := e.World.getArchetypeForComponents(
append(e.World.archetypes[archetypeIndex].Layout().Components(), c),
)
e.World.TransferArchetype(archetypeIndex, targetArchetype, e.loc.Component)
e.loc = e.World.Entry(e.entity).loc
}
if len(components) == 1 {
e.SetComponent(ctype, components[0])
e.SetComponent(c, components[0])
}
}

// RemoveComponent removes the component from the entity.
func (e *Entry) RemoveComponent(ctype component.IComponentType) {
if !e.Archetype().Layout().HasComponent(ctype) {
func (e *Entry) RemoveComponent(c component.IComponentType) {
if !e.Archetype().Layout().HasComponent(c) {
return
}

c := e.loc.Component
a := e.loc.Archetype

base_layout := e.World.archetypes[a].Layout().Components()
target_layout := make([]component.IComponentType, 0, len(base_layout)-1)
for _, c2 := range base_layout {
if c2 == ctype {
baseLayout := e.World.archetypes[e.loc.Archetype].Layout().Components()
targetLayout := make([]component.IComponentType, 0, len(baseLayout)-1)
for _, c2 := range baseLayout {
if c2 == c {
continue
}
target_layout = append(target_layout, c2)
targetLayout = append(targetLayout, c2)
}

target_arc := e.World.getArchetypeForComponents(target_layout)
e.World.TransferArchetype(e.loc.Archetype, target_arc, c)

targetArchetype := e.World.getArchetypeForComponents(targetLayout)
e.World.TransferArchetype(e.loc.Archetype, targetArchetype, e.loc.Component)
e.loc = e.World.Entry(e.entity).loc
}

Expand All @@ -159,8 +144,8 @@ func (e *Entry) Archetype() *storage.Archetype {
}

// HasComponent returns true if the entity has the given component type.
func (e *Entry) HasComponent(componentType component.IComponentType) bool {
return e.Archetype().Layout().HasComponent(componentType)
func (e *Entry) HasComponent(c component.IComponentType) bool {
return e.Archetype().Layout().HasComponent(c)
}

func (e *Entry) String() string {
Expand Down
16 changes: 13 additions & 3 deletions internal/storage/archetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package storage

import (
"github.com/yohamta/donburi/component"
"sync"
)

type ArchetypeIndex int
Expand All @@ -12,6 +13,15 @@ type Archetype struct {
index ArchetypeIndex
entities []Entity
layout *Layout
lock sync.Mutex
}

func (archetype *Archetype) Lock() {
archetype.lock.Lock()
}

func (archetype *Archetype) Unlock() {
archetype.lock.Unlock()
}

// NewArchetype creates a new archetype.
Expand Down Expand Up @@ -39,9 +49,9 @@ func (archetype *Archetype) ComponentTypes() []component.IComponentType {
}

// SwapRemove removes an entity from the archetype and returns it.
func (archetype *Archetype) SwapRemove(entity_index int) Entity {
removed := archetype.entities[entity_index]
archetype.entities[entity_index] = archetype.entities[len(archetype.entities)-1]
func (archetype *Archetype) SwapRemove(entityIndex int) Entity {
removed := archetype.entities[entityIndex]
archetype.entities[entityIndex] = archetype.entities[len(archetype.entities)-1]
archetype.entities = archetype.entities[:len(archetype.entities)-1]
return removed
}
Expand Down
13 changes: 11 additions & 2 deletions internal/storage/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Entity uint64
type EntityId uint32

const idMask Entity = 0xFFFFFFFF00000000
const versionMask Entity = 0xFFFFFFF
const versionMask Entity = 0x0FFFFFF
const readyMask Entity = 0x1000000

// NewEntity creates a new entity.
// The id is a unique identifier for the entity.
Expand All @@ -34,9 +35,17 @@ func (e Entity) Version() uint32 {
return uint32(e & Entity(versionMask))
}

func (e Entity) IsReady() bool {
return e&readyMask != 0
}

func (e Entity) Ready() Entity {
return e | readyMask
}

// IncVersion increments the entity version.
func (e Entity) IncVersion() Entity {
return e&idMask | ((e + 1) & versionMask)
return e&idMask | ((e+1)&versionMask)&^readyMask
}

func (e Entity) String() string {
Expand Down
8 changes: 3 additions & 5 deletions internal/storage/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ func (idx *Index) Push(layout *Layout) {
}

// SearchFrom searches for archetypes that match the given filter from the given index.
func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) *ArchetypeIterator {
iterator := &ArchetypeIterator{
current: 0,
}
func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) ArchetypeIterator {
iterator := ArchetypeIterator{current: 0}
iterator.values = []ArchetypeIndex{}
for i := start; i < len(idx.layouts); i++ {
if f.MatchesLayout(idx.layouts[i]) {
Expand All @@ -52,6 +50,6 @@ func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) *ArchetypeIterato
}

// Search searches for archetypes that match the given filter.
func (idx *Index) Search(filter filter.LayoutFilter) *ArchetypeIterator {
func (idx *Index) Search(filter filter.LayoutFilter) ArchetypeIterator {
return idx.SearchFrom(filter, 0)
}
4 changes: 2 additions & 2 deletions internal/storage/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func (it *EntityIterator) HasNext() bool {
}

// Next returns the next entity list.
func (it *EntityIterator) Next() []Entity {
func (it *EntityIterator) Next() *Archetype {
archetypeIndex := it.indices[it.current]
it.current++
return it.archetypes[archetypeIndex].Entities()
return it.archetypes[archetypeIndex]
}
26 changes: 13 additions & 13 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewStorage() *Storage {
// PushComponent stores the new data of the component in the archetype.
func (cs *Storage) PushComponent(component component.IComponentType, archetypeIndex ArchetypeIndex) {
if len(cs.storages) <= int(archetypeIndex) {
cs.ensureCapacity(archetypeIndex)
cs.ensureCapacity()
}
if v := cs.storages[archetypeIndex]; v == nil {
cs.storages[archetypeIndex] = []unsafe.Pointer{}
Expand All @@ -46,21 +46,21 @@ func (cs *Storage) SetComponent(archetypeIndex ArchetypeIndex, componentIndex Co
}

// MoveComponent moves the pointer to data of the component in the archetype.
func (cs *Storage) MoveComponent(source ArchetypeIndex, index ComponentIndex, dst ArchetypeIndex) {
if len(cs.storages) <= int(dst) {
cs.ensureCapacity(dst)
func (cs *Storage) MoveComponent(srcIndex ArchetypeIndex, index ComponentIndex, dstIndex ArchetypeIndex) {
if len(cs.storages) <= int(dstIndex) {
cs.ensureCapacity()
}

src_slice := cs.storages[source]
dst_slice := cs.storages[dst]
src := cs.storages[srcIndex]
dst := cs.storages[dstIndex]

value := src_slice[index]
src_slice[index] = src_slice[len(src_slice)-1]
src_slice = src_slice[:len(src_slice)-1]
cs.storages[source] = src_slice
value := src[index]
src[index] = src[len(src)-1]
src = src[:len(src)-1]
cs.storages[srcIndex] = src

dst_slice = append(dst_slice, value)
cs.storages[dst] = dst_slice
dst = append(dst, value)
cs.storages[dstIndex] = dst
}

// SwapRemove removes the pointer to data of the component in the archetype.
Expand All @@ -82,7 +82,7 @@ func (cs *Storage) Contains(archetypeIndex ArchetypeIndex, componentIndex Compon
return cs.storages[archetypeIndex][componentIndex] != nil
}

func (cs *Storage) ensureCapacity(archetypeIndex ArchetypeIndex) {
func (cs *Storage) ensureCapacity() {
newStorages := make([][]unsafe.Pointer, len(cs.storages)*2)
copy(newStorages, cs.storages)
cs.storages = newStorages
Expand Down
45 changes: 22 additions & 23 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,33 @@ type cache struct {
// So it is not recommended to create a new query every time you want
// to filter entities with the same query.
type Query struct {
layout_matches map[WorldId]*cache
filter filter.LayoutFilter
layoutMatches map[WorldId]*cache
filter filter.LayoutFilter
}

// NewQuery creates a new query.
// It receives arbitrary filters that are used to filter entities.
func NewQuery(filter filter.LayoutFilter) *Query {
return &Query{
layout_matches: make(map[WorldId]*cache),
filter: filter,
layoutMatches: make(map[WorldId]*cache),
filter: filter,
}
}

// Each iterates over all entities that match the query.
func (q *Query) Each(w World, callback func(*Entry)) {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
f := func(entity storage.Entity) {
entry := w.Entry(entity)
callback(entry)
}
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
for iter.HasNext() {
entities := iter.Next()
for _, entity := range entities {
f(entity)
archetype := iter.Next()
archetype.Lock()
for _, entity := range archetype.Entities() {
entry := w.Entry(entity)
if entry.entity.IsReady() {
callback(entry)
}
}
archetype.Unlock()
}
}

Expand All @@ -55,26 +55,25 @@ func (q *Query) EachEntity(w World, callback func(*Entry)) {
// Count returns the number of entities that match the query.
func (q *Query) Count(w World) int {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
ret := 0
for iter.HasNext() {
entities := iter.Next()
ret += len(entities)
archetype := iter.Next()
ret += len(archetype.Entities())
}
return ret
}

// First returns the first entity that matches the query.
func (q *Query) First(w World) (entry *Entry, ok bool) {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
if !iter.HasNext() {
return nil, false
}
for iter.HasNext() {
entities := iter.Next()
archetype := iter.Next()
entities := archetype.Entities()
if len(entities) > 0 {
return w.Entry(entities[0]), true
}
Expand All @@ -89,13 +88,13 @@ func (q *Query) FirstEntity(w World) (entry *Entry, ok bool) {

func (q *Query) evaluateQuery(world World, accessor *StorageAccessor) []storage.ArchetypeIndex {
w := world.Id()
if _, ok := q.layout_matches[w]; !ok {
q.layout_matches[w] = &cache{
if _, ok := q.layoutMatches[w]; !ok {
q.layoutMatches[w] = &cache{
archetypes: make([]storage.ArchetypeIndex, 0),
seen: 0,
}
}
cache := q.layout_matches[w]
cache := q.layoutMatches[w]
for it := accessor.Index.SearchFrom(q.filter, cache.seen); it.HasNext(); {
cache.archetypes = append(cache.archetypes, it.Next())
}
Expand Down
Loading
Loading