From ce2bc07ead8cc22156b306e0a4fa035dc9ca9cab Mon Sep 17 00:00:00 2001 From: Yota Hamada Date: Sun, 31 Mar 2024 15:43:24 +0900 Subject: [PATCH] improve thread safety (#131) --- entry.go | 89 ++++++++++++---------------- internal/storage/archetype.go | 16 ++++- internal/storage/entity.go | 13 ++++- internal/storage/index.go | 8 +-- internal/storage/iterator.go | 4 +- internal/storage/storage.go | 26 ++++----- query.go | 45 +++++++------- world.go | 107 ++++++++++++++++------------------ 8 files changed, 152 insertions(+), 156 deletions(-) diff --git a/entry.go b/entry.go index e14e56f..785c72e 100644 --- a/entry.go +++ b/entry.go @@ -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. @@ -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 } @@ -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 { diff --git a/internal/storage/archetype.go b/internal/storage/archetype.go index 5bdc928..67610c4 100644 --- a/internal/storage/archetype.go +++ b/internal/storage/archetype.go @@ -2,6 +2,7 @@ package storage import ( "github.com/yohamta/donburi/component" + "sync" ) type ArchetypeIndex int @@ -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. @@ -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 } diff --git a/internal/storage/entity.go b/internal/storage/entity.go index be1fe80..5022ec9 100644 --- a/internal/storage/entity.go +++ b/internal/storage/entity.go @@ -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. @@ -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 { diff --git a/internal/storage/index.go b/internal/storage/index.go index fa3db53..6304c84 100644 --- a/internal/storage/index.go +++ b/internal/storage/index.go @@ -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]) { @@ -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) } diff --git a/internal/storage/iterator.go b/internal/storage/iterator.go index df0c670..42cd1e2 100644 --- a/internal/storage/iterator.go +++ b/internal/storage/iterator.go @@ -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] } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index c1aeab1..36c05de 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -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{} @@ -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. @@ -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 diff --git a/query.go b/query.go index 57b48da..4290cc0 100644 --- a/query.go +++ b/query.go @@ -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() } } @@ -55,12 +55,11 @@ 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 } @@ -68,13 +67,13 @@ func (q *Query) Count(w World) int { // 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 } @@ -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()) } diff --git a/world.go b/world.go index bf1bf9e..c9b115f 100644 --- a/world.go +++ b/world.go @@ -2,7 +2,6 @@ package donburi import ( "fmt" - "github.com/yohamta/donburi/component" "github.com/yohamta/donburi/filter" "github.com/yohamta/donburi/internal/storage" @@ -110,8 +109,7 @@ func (w *world) CreateMany(num int, components ...component.IComponentType) []En } func (w *world) Create(components ...component.IComponentType) Entity { - archetypeIndex := w.getArchetypeForComponents(components) - return w.createEntity(archetypeIndex) + return w.createEntity(w.getArchetypeForComponents(components)) } func (w *world) createEntity(archetypeIndex storage.ArchetypeIndex) Entity { @@ -120,28 +118,30 @@ func (w *world) createEntity(archetypeIndex storage.ArchetypeIndex) Entity { componentIndex := w.components.PushComponents(archetype.Layout().Components(), archetypeIndex) w.entities.Insert(entity.Id(), archetypeIndex, componentIndex) archetype.PushEntity(entity) + + archetype.Lock() + defer archetype.Unlock() + w.createEntry(entity) + er := entity.Ready() + w.archetypes[archetypeIndex].Entities()[componentIndex] = er + w.entries[er.Id()].entity = er.Ready() for _, callback := range w.createCallbacks { - callback(w, entity) + callback(w, er) } - return entity + return er } -func (w *world) createEntry(e Entity) { +func (w *world) createEntry(e Entity) *Entry { id := e.Id() if int(id) >= len(w.entries) { - w.entries = append(w.entries, nil) - } - loc := w.entities.Location(id) - entry := &Entry{ - id: id, - entity: e, - loc: loc, - World: w, + w.entries = append(w.entries, &Entry{id: id, entity: e, loc: w.entities.Location(id), World: w}) + return w.entries[id] } - w.entries[id] = entry + w.entries[id].loc = w.entities.Location(id) + return w.entries[id] } func (w *world) Valid(e Entity) bool { @@ -152,11 +152,9 @@ func (w *world) Valid(e Entity) bool { return false } loc := w.entities.LocationMap[e.Id()] - a := loc.Archetype - c := loc.Component // If the version of the entity is not the same as the version of the archetype, // the entity is invalid (it means the entity is already destroyed). - return loc.Valid && e == w.archetypes[a].Entities()[c] + return e.IsReady() && loc.Valid && e == w.archetypes[loc.Archetype].Entities()[loc.Component] } func (w *world) Entry(entity Entity) *Entry { @@ -177,21 +175,18 @@ func (w *world) Remove(ent Entity) { for _, callback := range w.removeCallbacks { callback(w, ent) } - - loc := w.entities.LocationMap[ent.Id()] w.entities.Remove(ent.Id()) - w.removeAtLocation(ent, loc) + w.removeAtLocation(ent, w.entities.LocationMap[ent.Id()]) } } func (w *world) removeAtLocation(ent Entity, loc *storage.Location) { - arch_index := loc.Archetype - component_index := loc.Component - archetype := w.archetypes[arch_index] - archetype.SwapRemove(int(component_index)) - w.components.Remove(archetype, component_index) - if int(component_index) < len(archetype.Entities()) { - swapped := archetype.Entities()[component_index] + componentIndex := loc.Component + archetype := w.archetypes[loc.Archetype] + archetype.SwapRemove(int(componentIndex)) + w.components.Remove(archetype, componentIndex) + if int(componentIndex) < len(archetype.Entities()) { + swapped := archetype.Entities()[componentIndex] w.entities.Set(swapped.Id(), loc) } w.destroyed = append(w.destroyed, ent.IncVersion()) @@ -201,41 +196,41 @@ func (w *world) TransferArchetype(from, to storage.ArchetypeIndex, idx storage.C if from == to { return idx } - from_arch := w.archetypes[from] - to_arch := w.archetypes[to] + fromArchetype := w.archetypes[from] + toArchetype := w.archetypes[to] // move entity id - ent := from_arch.SwapRemove(int(idx)) - to_arch.PushEntity(ent) - w.entities.Insert(ent.Id(), to, storage.ComponentIndex(len(to_arch.Entities())-1)) + ent := fromArchetype.SwapRemove(int(idx)) + toArchetype.PushEntity(ent) + w.entities.Insert(ent.Id(), to, storage.ComponentIndex(len(toArchetype.Entities())-1)) - if len(from_arch.Entities()) > int(idx) { - moved := from_arch.Entities()[idx] - w.entities.Insert(moved.Id(), from, storage.ComponentIndex(idx)) + if len(fromArchetype.Entities()) > int(idx) { + moved := fromArchetype.Entities()[idx] + w.entities.Insert(moved.Id(), from, idx) } // creates component if not exists in new layout - from_layout := from_arch.Layout() - to_layout := to_arch.Layout() - for _, component_type := range to_layout.Components() { - if !from_layout.HasComponent(component_type) { - storage := w.components.Storage(component_type) - storage.PushComponent(component_type, to) + fromLayout := fromArchetype.Layout() + toLayout := toArchetype.Layout() + for _, c := range toLayout.Components() { + if !fromLayout.HasComponent(c) { + st := w.components.Storage(c) + st.PushComponent(c, to) } } // move components - for _, component_type := range from_layout.Components() { - storage := w.components.Storage(component_type) - if to_layout.HasComponent(component_type) { - storage.MoveComponent(from, idx, to) + for _, c := range fromLayout.Components() { + st := w.components.Storage(c) + if toLayout.HasComponent(c) { + st.MoveComponent(from, idx, to) } else { - storage.SwapRemove(from, idx) + st.SwapRemove(from, idx) } } w.components.Move(from, to) - return storage.ComponentIndex(len(to_arch.Entities()) - 1) + return storage.ComponentIndex(len(toArchetype.Entities()) - 1) } func (w *world) StorageAccessor() StorageAccessor { @@ -269,29 +264,29 @@ func (w *world) nextEntity() Entity { return entity } -func (w *world) insertArcheType(layout *storage.Layout) storage.ArchetypeIndex { +func (w *world) insertArchetype(layout *storage.Layout) storage.ArchetypeIndex { w.index.Push(layout) - arch_index := storage.ArchetypeIndex(len(w.archetypes)) - w.archetypes = append(w.archetypes, storage.NewArchetype(arch_index, layout)) + archetypeIndex := storage.ArchetypeIndex(len(w.archetypes)) + w.archetypes = append(w.archetypes, storage.NewArchetype(archetypeIndex, layout)) - return arch_index + return archetypeIndex } func (w *world) getArchetypeForComponents(components []component.IComponentType) storage.ArchetypeIndex { if len(components) == 0 { panic("entity must have at least one component") } - if ii := w.index.Search(filter.Exact(components)); ii.HasNext() { - return ii.Next() + if i := w.index.Search(filter.Exact(components)); i.HasNext() { + return i.Next() } if !w.noDuplicates(components) { panic(fmt.Sprintf("duplicate component types: %v", components)) } - return w.insertArcheType(storage.NewLayout(components)) + return w.insertArchetype(storage.NewLayout(components)) } func (w *world) noDuplicates(components []component.IComponentType) bool { - // check if there're duplicate values inside components slice + // check if there are duplicate values inside components slice for i := 0; i < len(components); i++ { for j := i + 1; j < len(components); j++ { if components[i] == components[j] {